From d10b7b954fd02bfcba50e0cefe56d02ece768b6e Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 2 Apr 2026 12:23:46 +0200 Subject: [PATCH 01/67] Specfied new, more user friendly pattern for pairing code and token --- .../discovery-pairing-authentication.md | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/website/docs/communication-layer/discovery-pairing-authentication.md b/website/docs/communication-layer/discovery-pairing-authentication.md index c41ee78..865deb0 100644 --- a/website/docs/communication-layer/discovery-pairing-authentication.md +++ b/website/docs/communication-layer/discovery-pairing-authentication.md @@ -411,17 +411,16 @@ The receiver of the service description **must** use the URL provided in the TXT ## The pairing token, the node ID alias and the pairing code -The pairing token is a random string of characters that is generated by the responder node. It is a secret which is transferred by the end user to the initiator node, and then is verified during the pairing process. Since there will be many cases where the end user has to manually type in the pairing token, the pairing token has to be short enough to make it easy for the end user to type in, but long enough to make it secure. The pairing token **must** be generated by a cryptographically secure pseudorandom number generator. The pairing token **must** be random binary data with a length of at least 9 bytes, and is encoded using Base64 before it is presented to the end user (9 bytes is equal to 12 characters when encoded with Base64). +The pairing token is a random string of characters that is generated by the responder node. It is a secret which is transferred by the end user to the initiator node, and then is verified during the pairing process. Since there will be many cases where the end user has to manually type in the pairing token, the pairing token has to be short enough to make it easy for the end user to type in, but long enough to make it secure. -The **pairing token** can be validated with the following regular expression: +The pairing token may consist out of lower case letters, upper case letters and numbers. The pairing token **must** be generated by a cryptographically secure pseudorandom number generator. The pairing token is typically dynamically generated when the user requests the pairing token at the user interface of the responder node. Dynamically generated pairing tokens **must** expire after a duration; five minutes is the recommended duration. However, energy flexible devices that do not have a user interface are allowed to have a static pairing token, that for example can be printed somewhere on the physical device. Static pairing tokens do not expire. Dynamic pairing tokens **must** contain at least 4 characters. Static paring tokens **must** contain at least 6 characters. Pairing tokens may be as long as the developer deems necessary. -``` -^(?:[A-Za-z0-9+\/]{4}){2,}(?:[A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}={2})$ -``` - -The pairing token is typically dynamically generated when the user requests the pairing token at the user interface of the responder node. Dynamically generated pairing tokens **must** expire after a duration; five minutes is the recommended duration. However, energy flexible devices that do not have a user interface are allowed to have a static pairing token, that for example can be printed somewhere on the physical device. Static pairing tokens do not expire. +| Type of pairing token | Minimal length | Validity | Regular expression | +| --- | --- | --- | --- | +| Dynamic pairing token | 4 characters | Limited duration, 5 minutes is recommended | `^[0-9a-zA-Z]{4,}$` | +| Static pairing token | 6 characters | Indefinitely | `^[0-9a-zA-Z]{6,}$` | -An endpoint can host multiple nodes. When attempting to pair a certain node, the endpoint needs to know exactly which of its nodes this pairing attempt is aimed at. nodes are uniquely identified with their node ID. Since this node ID is a UUID, it is pretty long and cumbersome to type in. That is why an endpoint can assign its nodes a *node ID alias*. This is an identifier that is intended to be short, and only unique within the context of this particular endpoint. Node ID aliases could be assigned by the endpoint whenever new nodes are created, but also could be generated dynamically only when someone is attempting to pair to this node. This way, node ID aliases have a short live, and can be reused by other nodes at other moments. This allows to use shorter node ID aliases. node ID aliases are a string of characters, which may include lower case letters, upper case letters and numbers. Node ID aliases are ideally as short as possible (at least one character), but should of course be long enough to allow the endpoint to uniquely identify an node. When an endpoint only contains one node, there is no need for a node ID alias. +An endpoint can host multiple nodes. When attempting to pair a certain node, the endpoint needs to know exactly which of its nodes this pairing attempt is aimed at. Nodes are uniquely identified with their node ID. Since this node ID is a UUID, it is pretty long and cumbersome to type in. That is why an endpoint can assign its nodes a *node ID alias*. This is an identifier that is intended to be short, and only unique within the context of this particular endpoint. Node ID aliases could be assigned by the endpoint whenever new nodes are created, but also could be generated dynamically only when someone is attempting to pair to this node. This way, node ID aliases have a short live, and can be reused by other nodes at other moments. This allows to use shorter node ID aliases. Node ID aliases are a string of characters, which may include lower case letters, upper case letters and numbers. Node ID aliases are ideally as short as possible (at least one character), but should of course be long enough to allow the endpoint to uniquely identify an node. When an endpoint only contains one node, there is no need for a node ID alias. The **node ID alias** can be validated with the following regular expression: @@ -429,7 +428,9 @@ The **node ID alias** can be validated with the following regular expression: ^[0-9a-zA-Z]+$ ``` -Although the pairing token and the node ID alias are two separate strings, which are treated as completely different in the pairing process, they are presented together to the user as one string: the *pairing code*. The pairing code is simply the node ID alias, followed by a dash ('-'), followed by the pairing token. When there is no node ID alias, the pairing code is simply identical to the pairing token. +Although the pairing token and the node ID alias are two separate strings, which are treated completely differently in the pairing process, they are presented together to the user as one string: the *pairing code*. The pairing code is simply the node ID alias, followed by a dash ('-'), followed by the pairing token. When there is no node ID alias, the pairing code is simply identical to the pairing token. + +> Note: The node ID alias is not secret and is exchanged during a pairing attempt. The pairing token is secret. It is never exchanged; it is only used as input for the challenge response function. ``` When no node ID alias is used (i.e. the endpoint only contains one node): @@ -441,7 +442,7 @@ When a node ID alias ID is used: Alternatively, the **pairing code** can be validated with the following regular expression: ``` -^(?:[0-9a-zA-Z]+-)?(?:[A-Za-z0-9+\/]{4}){2,}(?:[A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}={2})$ +^([0-9a-zA-Z]+-)?[0-9a-zA-Z]{4,}$ ``` The pairing code allows us to transfer two pieces of information by only bothering the end user once. Due to its format the initiator node can easily extract the node ID alias and the pairing token from the pairing code by splitting the string at the dash. From 937b4deebf6ba250188fddb317beab8c2b48e653 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Wed, 29 Apr 2026 18:03:57 +0200 Subject: [PATCH 02/67] Pull S2connect from learn docs --- website/docusaurus.config.ts | 13 +++++++++++++ .../discovery-pairing-authentication.md | 16 ++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) rename website/{docs/communication-layer => s2-connect}/discovery-pairing-authentication.md (99%) diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 3db14b4..0aec768 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -58,6 +58,13 @@ const config: Config = { path: "model-reference", routeBasePath: "model-reference", breadcrumbs: false, + }], + ['@docusaurus/plugin-content-docs', { + id: "s2c", + path: "s2-connect", + routeBasePath: "s2-connect", + breadcrumbs: false, + sidebarPath: false }] ], @@ -83,6 +90,12 @@ const config: Config = { position: 'left', label: 'Reference', }, + { + to: 's2-connect/discovery-pairing-authentication', + activeBaseRegex: 's2-connect', + position: 'left', + label: 'S2 Connect', + }, { href: 'https://github.com/flexiblepower/s2-documentation', label: 'GitHub', diff --git a/website/docs/communication-layer/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md similarity index 99% rename from website/docs/communication-layer/discovery-pairing-authentication.md rename to website/s2-connect/discovery-pairing-authentication.md index c41ee78..28df928 100644 --- a/website/docs/communication-layer/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -169,7 +169,7 @@ Nodes can be deployed locally within the LAN, or somewhere on a server in the WA * **WAN** nodes are typically part of a large application that run on many servers and/or on some kind of cloud computing platform. A single application usually serves many users. Each user could have one or multiple nodes. This could for example be a cloud-based energy management system that can connect to many devices. It could also be a cloud environment of a device manufacturer that hosts the RM instances in the cloud. An end user could own multiple devices from this manufacturer, thus the application could host multiple RM instances for this particular user. We call a group of nodes that a single user can manage within one application an *end user environment*. It is also possible that an end user environment contains both CEM and RM instances. The user interface is typically a web interface or a smartphone app. * **LAN** nodes are typically part of an application that runs on an embedded computer device somewhere in the building. Such a device could be a physical energy management system, an energy flexible device such as a home battery, heat pump or EV charger, or a gateway device which connects to an energy flexible device through some kind of protocol. Often an application will only host a single node, but it is also possible that an application hosts multiple nodes. A device could function completely on its own, but it cloud also be connected to an internet based application of the manufacturer. The user interface could be a physical human-machine interface on the device, but also be a smartphone app that connects directly to the device (e.g. via bluetooth), or a smartphone app or web interface that connects to an internet based application of the manufacturer. For energy flexible devices, it is assumed that they could also have no user interface at all, or that they are very constrained when it comes to computing power. It is assumed that a CEM always has a user interface. -![Deployment_options](../../static/img/communication-layer/deployment_options.png) +![Deployment_options](@site/static/img/communication-layer/deployment_options.png) There are three types of S2 connections between nodes possible: @@ -195,7 +195,7 @@ If pairing is performed successfully, the CEM and RM instances should establish Once a CEM is paired, the user has to possibility to command either of the nodes to *unpair*. After unpairing the CEM and RM instances can no longer communicate through S2 (unless the end user pairs them again). -![Pairing_process_user](../../static/img/communication-layer/pairing_process_user.png) +![Pairing_process_user](@site/static/img/communication-layer/pairing_process_user.png)
Image generated using the following PlantUML code: @@ -283,7 +283,7 @@ There are however two situations where this is not possible: * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement the pairing HTTP server in the cloud, even though the node itself is in the WAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. * **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTP server is far more memory intensive than an HTTP client, there is an option to implement a LAN RM instance purely as an HTTP client. A long-polling mechanism is available to indicate to the HTTP Server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTP server. In other words: in this specific situation the initiator node behaves as the HTTP server, and the responder node only has to be an HTTP client. -![Pairing_direction](../../static/img/communication-layer/pairing_direction.png) +![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) # Formal specification and versioning (normative) @@ -453,7 +453,7 @@ There are two possible types of certificates for TLS communication. The first op In the following image, the difference is shown. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. -![image.png](../../static/img/communication-layer/certificate-chains.png) +![image.png](@site/static/img/communication-layer/certificate-chains.png)
Image generated using the following PlantUML code: @@ -591,13 +591,13 @@ When the server sends the `requestPairing` action, the node on the client must a The activity diagram below summarizes the complete long-polling process from teh perspective of the client. -![image](../../static/img/communication-layer/long-polling_activity_diagram.png) +![image](@site/static/img/communication-layer/long-polling_activity_diagram.png) ## Pairing interaction The pairing process itself consists of several HTTP interactions between client and server. The image below depicts a successful pairing process between two nodes. -![image](../../static/img/communication-layer/pairing_http_process.png) +![image](@site/static/img/communication-layer/pairing_http_process.png)
Image generated using the following PlantUML code: @@ -923,7 +923,7 @@ There are four scenarios for CEM and RM deployment, and applying the rules above During the pairing process an `accessToken` is generated by the node which will be the communication server and sent to the node that will be the communication client. This `accessToken` can be used by the communication client to set up a session with the communication server for exchanging S2 messages. The `accessToken` does not expire, but it can only be used (successfully) once to set up a connection. Each time a new connection is made, the `accessToken` will be renewed. The communication server will generate a new `accessToken` and sends it to the communication client. Since this `accessToken` is the only means to connect two nodes once they are paired, the connection initiation process makes sure that both nodes confirm that they have successfully persisted the new `accessToken` before invalidating the old `accessToken`. -![connection initiation](../../static/img/communication-layer/connection-initiation.png) +![connection initiation](@site/static/img/communication-layer/connection-initiation.png)
Image generated using the following PlantUML code: @@ -1148,7 +1148,7 @@ The S2 standard has been encoded into a JSON schema specification, for details s ### State of communication -![State of Communication](../../static/img/communication-layer/state-of-communication.png) +![State of Communication](@site/static/img/communication-layer/state-of-communication.png)
Image generated using the following PlantUML code: From 45bf51b547667fbd2600a14dcf697c0d81f43bda Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Mon, 4 May 2026 15:21:50 +0200 Subject: [PATCH 03/67] Move certificate specification to security section + use S2 Connect as name of the spec --- .../communication-layer/accepted-crypto.md | 4 +- .../discovery-pairing-authentication.md | 110 +++++---- .../security-considerations.md | 214 ------------------ 3 files changed, 53 insertions(+), 275 deletions(-) delete mode 100644 website/docs/communication-layer/security-considerations.md diff --git a/website/docs/communication-layer/accepted-crypto.md b/website/docs/communication-layer/accepted-crypto.md index 31faf79..d83ce33 100644 --- a/website/docs/communication-layer/accepted-crypto.md +++ b/website/docs/communication-layer/accepted-crypto.md @@ -5,7 +5,7 @@ sidebar_position: 4 ## Introduction -In the [discovery and pairing specification](./discovery-pairing-authentication.md) a description is given of the required communication layer. This layer includes a description of encryption requirements. Because the security levels given by different crypto algorithms change continuously, this page contains the accepted crypto algorithms for the S2 protocol. Keep in mind that this will change over time. +The S2 Connect specification contains a description of encryption requirements. Because the security levels given by different crypto algorithms change continuously, this page contains the accepted crypto algorithms for S2 Connect. Keep in mind that this will change over time. At the moment, we refer to the Mozilla guidelines in: [Server Side TLS](https://wiki.mozilla.org/Security/Server_Side_TLS). In the future these Mozilla guidelines will change, and S2 might have it's own list of accepted algorithms. @@ -13,7 +13,7 @@ At the moment, we refer to the Mozilla guidelines in: [Server Side TLS](https:// As mentioned in the introduction, due developments in crypto technologies, security levels of known crypto algorithms will change over time. Algorithms that were considered safe 10 years ago, are unsafe at the moment. And algorithms that are considered safe today, might be vulnerable to new attacks in 5 years, or even tomorrow. And as we prepare for the imminent transition to post-quantum cryptography, our perception of the security of algorithms will most likely change. -Therefore, this list will continuously change, and kept up to date. However, to engineer future proof systems, S2-nodes **SHOULD** be able to update their used crypto libraries, and hardware **SHOULD** be over-dimensioned. So, if the used crypto becomes obsolete newer, and possibly heavier, algorithms can be installed on the S2-node. When changes are made to the list of accepted crypto libraries, all S2Nodes **MUST** follow these changes within half a year. +Therefore, this list will continuously change, and kept up to date. However, to engineer future proof systems, S2 nodes **SHOULD** be able to update their used crypto libraries, and hardware **SHOULD** be over-dimensioned. So, if the used crypto becomes obsolete newer, and possibly heavier, algorithms can be installed on the S2 node. When changes are made to the list of accepted crypto libraries, all S2Nodes **MUST** follow these changes within half a year. ## Accepted algorithms diff --git a/website/docs/communication-layer/discovery-pairing-authentication.md b/website/docs/communication-layer/discovery-pairing-authentication.md index 865deb0..0ef3b80 100644 --- a/website/docs/communication-layer/discovery-pairing-authentication.md +++ b/website/docs/communication-layer/discovery-pairing-authentication.md @@ -84,7 +84,7 @@ For the long answer, please refer to [this page](why-not-oauth.md). ## Security requirements -The described protocol, ensures the following 4 requirements: +The S2 Connect protocol ensures the following four requirements: 1. Mutual authentication 2. Integrity of communication @@ -96,12 +96,11 @@ There is one guarantee that explicitly is not given by this protocol: 5. Non-repudiation - ### 1. Mutual authentication (guaranteed) -The mutual authentication is based on the trust relation between the user and the Client/Server. Since it is assumed that the user already had a trust relation with both of them, this existing trust can be used for mutual authentication between the client and the server. Note that the this communication is not part of the S2 protocol. +The mutual authentication is based on the trust relation between the user and the Client/Server. Since it is assumed that the user already had a trust relation with both of them, this existing trust can be used for mutual authentication between the client and the server. Note that the this communication is not part of the S2 Connect specification. -The end user requests an url, and token from the server, and gives these to the client. Based on these data, the client can connect to the server, using a TLS connection, check the certificate and authenticate himself with the token. Note that if the server uses a self-signed certificate, the fingerprint will be shared during the pairing phase, so it can be verified by the client. +The end user requests a URL, and token from the server, and gives these to the client. Based on these data, the client can connect to the server, using a TLS connection, check the certificate and authenticate himself with the token. Note that if the server uses a self-signed certificate, the fingerprint will be shared during the pairing phase, so it can be verified by the client. ### 2. Integrity of communication (guaranteed) @@ -121,7 +120,7 @@ Non-repudiation is not guaranteed in this protocol. Individual messages are not ### Remaining risk -There are two remaining vulnerable situations for the described protocol. In this section both will be explained. +There are two remaining vulnerable situations. In this section both will be explained. #### self-signed certificates @@ -129,7 +128,7 @@ In the case that a local RM and a local CEM communicate, it is not in every situ #### Trust relations between the end-user and the Client/Server -The entire trust model of S2 is based on the fact that there is already a trust relation between the end-user and the client/server. If these clients/servers do not use adequate security mechanisms, it might be possible to attack the S2 system as well. +The entire trust model of S2 Connect is based on the fact that there is already a trust relation between the end-user and the client/server. If these clients/servers do not use adequate security mechanisms, it might be possible to attack the S2 system as well. # Terms and definitions (normative) @@ -448,53 +447,6 @@ Alternatively, the **pairing code** can be validated with the following regular The pairing code allows us to transfer two pieces of information by only bothering the end user once. Due to its format the initiator node can easily extract the node ID alias and the pairing token from the pairing code by splitting the string at the dash. -## TLS Certificates - -There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local domain. - -In the following image, the difference is shown. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. - -![image.png](../../static/img/communication-layer/certificate-chains.png) - -
-Image generated using the following PlantUML code: - -``` -@startuml -struct PublicRootCA -struct PublicIntermediateCA -struct PublicServerCertificate - -PublicRootCA --> PublicIntermediateCA -PublicIntermediateCA --> PublicServerCertificate - - -struct SelfSignedCA -struct LocalServerCertificate - -SelfSignedCA --> SelfSignedCA -SelfSignedCA --> LocalServerCertificate -@enduml -``` -
- - -### Trusting a self-signed root certificate - -The self-signed root certificate is by default not trusted. However during the pairing phase, the server with the self-signed root certificate will share the fingerprint of the certificate during the pairing phase as part of the challenge. This will enable the client to verify the self-signed root certificate, and create trust. From this moment on, the client will store the complete fingerprint of the self-signed root certificate, and use it to verify the server certificate for all future connections. - -Note that the `preparePairing` and `cancelPreparePairing` endpoints can be called before the pairing has happened. So in the case the server is running on a LAN (and thus uses self-signed certificates), the client can skip the certificate validation steps on those endpoint. This means that the HTTP client **must** be configured to accept self-signed certificates during the pairing process. Since the pairing process consists of several HTTP requests, the HTTP client **must** check that for every request the same self-signed certificate is used by the HTTP server. If this is not the case, the HTTP client **cannot** proceed with the request. - - - -### Updating the certificates - -A server can update its certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. - -If the server is in local-local mode, and uses a self-signed CA certificate, the CA certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server. If the used crypto for the the CA certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its server certificate at least once every 6 months. - - - ## Challenge response process This protocol uses a two-way challenge response process to verify that both nodes have the same pairing token. For this process it doesn't matter which node has issued the pairing token and which node has the pairing token that was entered by the end user. The reason a two-way challenge response process is used to verify the pairing token is that it allows to establish trust without having to expose the pairing token. Both a challenge and a response are binary data, which are encoded using Base64. @@ -1202,16 +1154,56 @@ Client and server **can** keep other (non-security) information for, for example > TODO: This section needs to be expanded to explain measures against ddos -Please refer to an extensive description of the security specifications to [Security considerations](./security-considerations.md). +## Brute-force protection +To prevent brute-force pairing request, the server **MUST** implement rate limiting on the requestPairing endpoint. It is up to the server implementation to define the type of rate limiting. + + +## TLS Certificates + +All HTTP and WebSocket communication uses TCP over TLS with server certificates. The server certificates **MUST** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. + +There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local domain name. + +The following image shows the difference. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. + +![image.png](../../static/img/communication-layer/certificate-chains.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +struct PublicRootCA +struct PublicIntermediateCA +struct PublicServerCertificate + +PublicRootCA --> PublicIntermediateCA +PublicIntermediateCA --> PublicServerCertificate + -## Certificates +struct SelfSignedCA +struct LocalServerCertificate -For each S2 connection the server authenticates using a TLS certificate. The cloud implementation certificates **MUST** be PKI certificates which are not self-signed. Only local servers can use a self-signed root certificate, which is used to sign a server certificate. -If the S2 protocol is used in a local-local configuration, the server **CAN** use a self-signed root certificate. In this case, the pairingInfo **MUST** include the first 9 bytes, encodes as 12 base64 encoded characters, of the fingerprint of this self-signed CA certificate and the client **MUST** check this fingerprint. +SelfSignedCA --> SelfSignedCA +SelfSignedCA --> LocalServerCertificate +@enduml +``` +
-Note that all communication uses TLS. This is further explained in [Security considerations](./security-considerations.md). -The server certificates **MUST** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. +### Trusting a self-signed root certificate + +The self-signed root certificate is by default not trusted. However during the pairing phase, the server with the self-signed root certificate will share the fingerprint of the certificate during the pairing phase as part of the challenge. This will enable the client to verify the self-signed root certificate, and create trust. In this case, the [pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code) **MUST** include the first 9 bytes, encodes as 12 base64 encoded characters, of the fingerprint of this self-signed CA certificate and the client **MUST** check this fingerprint. From this moment on, the client will store the complete fingerprint of the self-signed root certificate, and use it to verify the server certificate for all future connections. + +Note that the `preparePairing` and `cancelPreparePairing` endpoints can be called before the pairing has happened. So in the case the server is running on a LAN (and thus uses self-signed certificates), the client can skip the certificate validation steps on those endpoint. This means that the HTTP client **must** be configured to accept self-signed certificates during the pairing process. Since the pairing process consists of several HTTP requests, the HTTP client **must** check that for every request the same self-signed certificate is used by the HTTP server. If this is not the case, the HTTP client **cannot** proceed with the request. + + +### Updating the certificates + +A server can update its certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. + +If the server is in local-local mode, and uses a self-signed CA certificate, the CA certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server. If the used crypto for the the CA certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its server certificate at least once every 6 months. + ## Cipher suites diff --git a/website/docs/communication-layer/security-considerations.md b/website/docs/communication-layer/security-considerations.md deleted file mode 100644 index 3c4efce..0000000 --- a/website/docs/communication-layer/security-considerations.md +++ /dev/null @@ -1,214 +0,0 @@ ---- -title: Security Considerations -sidebar_position: 3 ---- - -## Crypto requirements - -The described protocol, ensures the following 4 requirements: - -1. Mutual authentication -2. Integrity of communication -3. Confidentiality of communication -4. Forward secrecy - -There is one guarantee that explicitly is not given by this protocol: - -1. Non-repudiation - -## Pairing and connection initiation processes - -In the image below, the relevant security communication is visualized. Note that, the first part of the communication (in red) is not part of the S2 protocol. The developer of the corresponding Client / Server is responsible for relevant security mechanisms in this part of the communication. This is important to note because the trust between the end user and both systems is the basis for the trust in the further communication. - -The following sequence diagram focuses on validation of certificates and tokens. Please refer to the other sequence diagrams that contain more details about the pairing and connection initiation. - -![image](../../static/img/communication-layer/pairing-security.png) - -
-Image generated using the following PlantUML code: - -``` -@startuml -participant Client -participant EndUser -participant Server -note over Client, Server : Pairing phase -note left of Client: The red part of the communication \nis not part of the S2 protocol. \n\nUsed crypto is defined by the developer \nof the Client/Server - - -'PRE-S2 communication -EndUser-[#red]>Server: Request pairingInfo -Server -[#red]> Server: Create a connection token -note over Server: The pairing token expires\nafter 5 minutes -Server-[#red]>EndUser: Provide pairingInfo (url, certificate fingerprint and pairing token) -EndUser-[#red]>Client: Provide pairingInfo (url, certificate fingerprint and pairing token) -note left of Client : From this point on, S2 is used - -'Setting up connection -Client-[#blue]> Server : Setup TLS connection -Client -[#blue]> Client : Check certificate -alt Self-signed CA certificate -Client -[#blue]> Client : Check if server is local -alt Server is not local -Client -[#blue]> Client : Disconnect. Pairing failed. -end -Client -[#blue]> Client : Check certificate fingerprint -alt Certificate fingerprint does not match -Client -[#blue]> Client : Disconnect. Pairing failed. -end -else Certificate does not validate -Client -[#blue]> Client : Disconnect. Pairing failed. -end - -'Pairing with server -Client-[#blue]> Server : HTTPS requestPairing(token, clientNodeId) -Server -[#blue]> Server: Check pairing token -alt Pairing token is not valid or expired -Server --[#blue]> Client: HTTPS requestPairingResponse(error) -else Pairing token valid -Server -[#blue]> Server: Generate accessToken -Server --[#blue]> Client : HTTPS requestPairingResponse(accessToken) -end - -'Setting up connection -note over Client, Server : Future connections -Client -[#blue]> Server: Setup TLS connection -Client -[#blue]> Client: Check certificate - -alt Self-signed CA certificate -Client -[#blue]> Client : Check if server is local -alt Server is not local -Client -[#blue]> Client : Disconnect. Connection failed. -end -Client -[#blue]> Client : Check if certificate fingerprint -alt Certificate unknown -Client -[#blue]> Client : Disconnect. Connection failed. -end -else Certificate does not validate -Client -[#blue]> Client : Disconnect. Connection failed. -end -Client -[#blue]> Server: HTTPS initiateConnection(accessToken, s2ClientNodeId) -Server -[#blue]> Server: Check accessToken - -alt accessToken valid -Server --[#blue]> Client: initiateConnectionResponse(connectionUrl, new accessToken, commToken) -else accessToken invalid -Server --[#blue]> Client: initiateConnectionResponse(401) -end - - -'Websocket connection -alt -Client -[#blue]> Server: Connect to websocket with commToken \nover existing TLS connection -else -Client -[#blue]> Server: Setup TLS connection -Client -[#blue]> Client: Check certificate - -alt Self-signed CA certificate -Client -[#blue]> Client : Check if server is local -alt Server is not local -Client -[#blue]> Client : Disconnect. Connection failed. -end -Client -[#blue]> Client : Check if certificate fingerprint -alt Certificate unknown -Client -[#blue]> Client : Disconnect. Connection failed. -end -else Certificate does not validate -Client -[#blue]> Client : Disconnect. Connection failed. -end -Client -[#blue]> Server: open WebSocket connection with commToken -end -Server -[#blue]> Server: Check commToken -alt Incorrect token -Server -[#blue]> Server : Disconnect. Connection failed. -end -Server -[#blue]> Client : Connection accepted. -@enduml - -``` - -
- -## Certificates - -There are two possible types of certificates. The first option is a public server certificate, that is part of the public PKI infrastructure, (indirectly) signed by a public root CA. This protocol allows local servers to use a self signed CA certificate to sign its local server certificate. This is needed because a local server is not able to get a certificate from a public PKI infrastructure. - -In the following image, the difference is shown. On the left a public root CA that's publicly known and trusted, on the right, a self signed root certificate, that's unknown and it's trustworthiness has to be achieved in another way. - -(../../static/imgge.png](/img/communication-layer/certificate-chains.png) - -
-Image generated using the following PlantUML code: - -``` -@startuml -struct PublicRootCA -struct PublicIntermediateCA -struct PublicServerCertificate - -PublicRootCA --> PublicIntermediateCA -PublicIntermediateCA --> PublicServerCertificate - - -struct SelfSignedCA -struct LocalServerCertificate - -SelfSignedCA --> SelfSignedCA -SelfSignedCA --> LocalServerCertificate -@enduml -``` -
- - -### Trusting a self-signed root certificate - -The self signed root certificate is by default not trusted. However during the pairing phase, the server with the self signed root certificate will share part of the root's certificate fingerprint as part of the pairing token, via a second channel. This will enable the client to verify the self signed root certificate, and create trust. From this moment on, the client will store the complete fingerprint of the self signed root certificate, and use it to verify the server certificate for all future connections. - -Note that the `preparePairing` and `cancelPreparePairing` endpoints can be called before the pairing has happened. So in the case the server is running on a LAN (and thus uses self-signed certificates), the client can skip the certificate validation steps on those endpoint. - -### Updating the certificates - -A server can update its certificate. When a cloud server updates it's certificate, it **MUST** be signed by a CA, so a client can check it's validity. A server **SHOULD** update its server certificate at least once every 6 months. - -If the server is in local-local mode, and uses a self-signed CA certificate, the CA certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server. If the used crypto for the the CA certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its server certificate at least once every 6 months. - -## Brute-force protection -To prevent brute-force pairing request, the server MUST implement rate limiting on the requestPairing endpoint. It is up to the server implementation to define the type of rate limiting. - -## How are the requirements met? - -In the follow section, reasoning for each security requirement will be given. - -### Mutual authentication - -The mutual authentication is based on the trust relation between the user and the Client/Server. Since it is assumed that the user already had a trust relation with both of them, this existing trust can be used for mutual authentication between the client and the server. Note that the this communication is not part of the S2 protocol. - -The enduser requests an url, certificate fingerprint and token from the server, and gives these to the client. Based on these data, the client can connect to the server, using a TLS connection, check the certificate and authenticate himself with the token. Note that if the server uses a self-signed certificate, it will also give a certificate fingerprint to the user. The client needs to use this fingerprint to verify the certificate in the TLS connection. - -### Integrity of communication - -Using TLS will ensure the integrity of the data. - -### Confidentiality of communication - -Using TLS will ensure the confidentiality of the data. - -### Forward secrecy - -Using TLS1.3 will ensure the forward secrecy of the data. - -### Non-repudiation - -Non-repudiation is not guaranteed in this protocol. Individual messages are not signed by anyone and as a result both parties could deny sending a specific request. However, while no legal proof is given, since integrity and authenticity is guaranteed by TLS, each party always knows for sure which party made what statement. - -## Remaining risk - -There are two remaining vulnerable situations for the described protocol. In this section both will be explained. - -### Self signed certificates - -In the case that a local RM and a local SEM communication, it is not possible to generate a PKI-certificate that can be publicly validated. As a result, S2 accepts in **ONLY** this situation self-signed certificates. The risk for spoofing attacks are mitigated by sharing the certificate fingerprint and pinning the self signed certificate at the client side. As a result, the client can check for all future connections whether or not it is connected with the same server. - -### Trust relations between the end-user and the Client/Server - -The entire trust model of S2 is based on the fact that there is already a trust relation between the end-user and the client/server. If these clients/servers do not use adequate security mechanisms, it might be possible to attack the S2 system as well. \ No newline at end of file From bdffbdda3a3686bce8697aeea11c9158ee91e002 Mon Sep 17 00:00:00 2001 From: GerbenBroenink Date: Tue, 5 May 2026 08:50:36 +0200 Subject: [PATCH 04/67] Update discovery-pairing-authentication.md added that a self signed certificate can only be used when a public PKI is impossible --- .../communication-layer/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/communication-layer/discovery-pairing-authentication.md b/website/docs/communication-layer/discovery-pairing-authentication.md index 0ef3b80..6763dab 100644 --- a/website/docs/communication-layer/discovery-pairing-authentication.md +++ b/website/docs/communication-layer/discovery-pairing-authentication.md @@ -1162,7 +1162,7 @@ To prevent brute-force pairing request, the server **MUST** implement rate limit All HTTP and WebSocket communication uses TCP over TLS with server certificates. The server certificates **MUST** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. -There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local domain name. +There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local domain name. This is also the only situation where self-signed certificates are allowed. The following image shows the difference. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. From fea405324e0abf1d55cdb8445cd92d6d083d16ae Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Wed, 6 May 2026 17:20:02 +0200 Subject: [PATCH 05/67] created v1.0-beta2 version --- .../discovery-pairing-authentication.md | 2 +- .../discovery-pairing-authentication.md | 1210 +++++++++++++++++ website/s2c_versions.json | 3 + 3 files changed, 1214 insertions(+), 1 deletion(-) create mode 100644 website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md create mode 100644 website/s2c_versions.json diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 291086e..c23b78d 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1166,7 +1166,7 @@ There are two possible types of certificates for TLS communication. The first op The following image shows the difference. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. -![image.png](../../static/img/communication-layer/certificate-chains.png) +![image.png](@site/static/img/communication-layer/certificate-chains.png)
Image generated using the following PlantUML code: diff --git a/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md b/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md new file mode 100644 index 0000000..291086e --- /dev/null +++ b/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md @@ -0,0 +1,1210 @@ +--- +title: S2 Connect Specification +sidebar_position: 2 +--- + +# S2 Connect - Specification + +# Introduction + +S2 Connect is a JSON based protocol specification implementing the EN50491-12-2 "S2" standard for home and building energy management. + +This specification addresses everything needed to created a secure and interoperable implementation of the S2 standard. The communication layer concerns the discovery, the pairing, the application layer communication protocol, the authentication, the message data model and the serialization. It was specifically designed to support multiple deployment scenario's and to give a relative consistent user experience throughout these different scenario's. + +The protocol is designed to specify communication between two devices, a resource (e.g. a heat pump or EV charger) and a (home) energy management system. It is worth noting that, while this specification focuses on describing the interaction between two components, a (home) energy management is likely to be communicating with multiple resources at the same time. + +# Version + +This version of this specification is based on the following versions of the underlying formal specification files (see [Formal specification and versioning (normative)](#formal-specification-and-versioning-normative) for more details). + +| Project | Files | Version | Reference | +| --- | --- | --- | --- | +| S2 Connect | OpenAPI files | `v1.0-beta-2` | [Github](https://github.com/flexiblepower/s2-connect) | +| S2 JSON | JSON schemas | `v0.02-beta` | [Github](https://github.com/flexiblepower/s2-ws-json) | + +> Note: S2 Connect is not directly linked to the version of S2 JSON. The exact version of S2 JSON that is being used by the CEM and RM is negotiated during connection initiation. + +# List of abbreviations + +|Abbreviation | Meaning +|---|---| +| CEM | Customer Energy Manager | +| DNS | Domain Name System | +| DNS-SD | DNS Service Discovery | +| HTTP | HyperText Transfer Protocol | +| LAN | Local Area Network (i.e. a local network, typically constrained to the building) | +| mDNS | Multicast DNS | +| NAT | Network Address Translation | +| REST | Representational state transfer | +| RM | Resource Manager | +| S2 | European standard on Energy Flexibility EN50491-12-2 | +| UUID | Universally Unique IDentifier (see [RFC 9562](https://www.rfc-editor.org/rfc/rfc9562)) | +| WAN | Wide Area Network (i.e. the public internet) | + +# Background (informative) + +## Requirements + +> TODO: This section needs to be rewritten to better explain the design goals + +The communication layer meets the following requirements: + +The Customer Energy Manager (CEM) and Resource Manager (RM) are logical concepts within the S2 architecture, therefore the S2 standard does not make any assumptions on how and where the CEM and RM are deployed in a real life situation. In practice, the CEM could be deployed on a local gateway in a LAN or as a server somewhere on the internet (WAN), while the RM could be part of the device itself, deployed on an add-on module or on the internet as well. This means that the S2 communication layer **MUST** be able to deal with multiple scenarios that are depicted in the figure below. + +In addition to - and partly because of - supporting the various deployment options, the S2 communication layer has the following generic requirements: + +- Support for full duplex communication. Both sides **MUST** be able to send and receive data simultaneously. +- Communication **MUST** be IP based. +- Communication **MUST** be encrypted. +- Communication latency between CEM and RM or vice versa **MUST** be ≤ 1 second. +- Communication **MUST** work without additional firewall configuration by the end user. +- Implementation of the communication layer **MUST** be based on a widely accepted technology and must be relatively easy to implement. +- The pairing process **SHOULD** support extensibility for other application layer communication protocols. +- Provide a relatively consistent user experience regardless of the deployment of the node +- Run a local RM on a device with constrained hardware +- A RM could not have a UI + +## Technical decisions +Given the requirements, this specification is build on the following high-level technical choices: + +Application layer communication protocol: WebSocket Secure with bearer token authentication. + +Pairing: Custom HTTP API specified in OpenAPI. + +Discovery: DNS-SD (within a LAN) in combination with a central registry (for WAN deployments). + +Serialization: json. + + +**Why not oAuth 2.0?** + +The short answer is: oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. We have identified a way to make it work but since it is such non-typical way, we choose not to use oAuth 2.0. + +For the long answer, please refer to [this page](why-not-oauth.md). + +## Security requirements + +The S2 Connect protocol ensures the following four requirements: + +1. Mutual authentication +2. Integrity of communication +3. Confidentiality of communication +4. Forward secrecy + +There is one guarantee that explicitly is not given by this protocol: + +5. Non-repudiation + + +### 1. Mutual authentication (guaranteed) + +The mutual authentication is based on the trust relation between the user and the Client/Server. Since it is assumed that the user already had a trust relation with both of them, this existing trust can be used for mutual authentication between the client and the server. Note that the this communication is not part of the S2 Connect specification. + +The end user requests a URL, and token from the server, and gives these to the client. Based on these data, the client can connect to the server, using a TLS connection, check the certificate and authenticate himself with the token. Note that if the server uses a self-signed certificate, the fingerprint will be shared during the pairing phase, so it can be verified by the client. + +### 2. Integrity of communication (guaranteed) + +Using TLS will ensure the integrity of the data. + +### 3. Confidentiality of communication (guaranteed) + +Using TLS will ensure the confidentiality of the data. + +### 4. Forward secrecy (guaranteed) + +Using TLS1.3 will ensure the forward secrecy of the data. + +### 5. Non-repudiation (NOT guaranteed) + +Non-repudiation is not guaranteed in this protocol. Individual messages are not signed by anyone and as a result both parties could deny sending a specific request. However, while no legal proof is given, since integrity and authenticity is guaranteed by TLS, each party always knows for sure which party made what statement. + +### Remaining risk + +There are two remaining vulnerable situations. In this section both will be explained. + +#### self-signed certificates + +In the case that a local RM and a local CEM communicate, it is not in every situation possible to generate a PKI-certificate that can be publicly validated. As a result, S2 accepts, **ONLY** in this situation, self-signed certificates. The risk for spoofing attacks are mitigated by including the certificate fingerprint in the challenge-response process as part of the pairing process, and pinning the self-signed CA certificate at the client side. As a result, the client can check for all connections whether or not it is connected with the correct server. + +#### Trust relations between the end-user and the Client/Server + +The entire trust model of S2 Connect is based on the fact that there is already a trust relation between the end-user and the client/server. If these clients/servers do not use adequate security mechanisms, it might be possible to attack the S2 system as well. + +# Terms and definitions (normative) + +This specification uses the concepts that are defined below. + +| Term | Definition | +| --- | --- | +| Access token | A token that is used for setting up an S2 connection. It is random binary data and must be generated by a cryptographically secure pseudorandom number generator and have a minimum length of 32 bytes. It is encoded using Base64. Also see [11A. Response status 200](#11a-response-status-200). | +| Communication client | The nodes which behaves as the HTTP client when initiating an S2 connection. | +| Communication server | The nodes which behaves as the HTTP server when initiating an S2 connection. | +| End user | A person or entity that manages nodes. For the purpose of this specification it is assumed that there is already a trust relationship in place between this person and the nodes. This means that the way the trust relationship has been established is out of scope for this specification. | +| End user environment | A restricted area within an application that contains all the nodes that belong to the end user. Also see [Deployment of nodes](#deployment-of-nodes). | +| Endpoint | A service which can handle pairing requests or initiates pairing requests itself. An endpoint can represent one node, but could also represent many. | +| Initiator node | The node that takes the initiative to pair with a responder node. This is typically the node from which the user initiates the pairing process. It is the counterpart of the responder node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | +| Node | Refers to an instance of either a CEM or a RM as defined in EN 50491-12-1 and implementing this specification. S2 communication between two nodes can only be established if one of the nodes is a CEM and the other a RM. These nodes must also have the same end user. | +| Node ID | A globally unique identifier for an node in the UUID format. | +| Node ID alias | A short identifier for an node, which is unique in the context of a single endpoint. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Pairing attempt | The process of pairing two nodes. The process can be completed successfully or unsuccessfully. | +| Pairing client | The endpoint which behaves as the HTTP client when pairing with an node. | +| Pairing code | The pairing code is the string of characters the end user has to copy from the responder node user interface to the initiator node user interface, in order to pair the two nodes. The pairing code consists of a pairing token and if required a node ID alias. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Pairing endpoint registry | The central registry that keeps track of publicly available pairing servers. | +| Pairing server | The endpoint which behaves as the HTTP server when pairing with an node. | +| Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and an node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | +| User interface | A user interface through which an end user can interact with an node. Interactions between the end user and the user interface must be secure, but this is out of scope for this specification. Examples of a user interface are a web interface, an app or a physical interface (HMI) on a device. | + + +# Architecture (informative) +This section explains the overall architecture and deployment options for CEM and RM instances. + +## Deployment of nodes + +This specification is concerned with connecting an instance of a CEM with an instance of a RM. Either of these instances are referred to as *nodes*, which either have the CEM *role* or the RM *role*. Obviously, it is only possible to pair an node with the CEM role to an node with the RM role. + +Nodes can be deployed locally within the LAN, or somewhere on a server in the WAN. Although their deployment doesn't significantly affect the working of these nodes, there are some key differences between these types of deployment. + +* **WAN** nodes are typically part of a large application that run on many servers and/or on some kind of cloud computing platform. A single application usually serves many users. Each user could have one or multiple nodes. This could for example be a cloud-based energy management system that can connect to many devices. It could also be a cloud environment of a device manufacturer that hosts the RM instances in the cloud. An end user could own multiple devices from this manufacturer, thus the application could host multiple RM instances for this particular user. We call a group of nodes that a single user can manage within one application an *end user environment*. It is also possible that an end user environment contains both CEM and RM instances. The user interface is typically a web interface or a smartphone app. +* **LAN** nodes are typically part of an application that runs on an embedded computer device somewhere in the building. Such a device could be a physical energy management system, an energy flexible device such as a home battery, heat pump or EV charger, or a gateway device which connects to an energy flexible device through some kind of protocol. Often an application will only host a single node, but it is also possible that an application hosts multiple nodes. A device could function completely on its own, but it cloud also be connected to an internet based application of the manufacturer. The user interface could be a physical human-machine interface on the device, but also be a smartphone app that connects directly to the device (e.g. via bluetooth), or a smartphone app or web interface that connects to an internet based application of the manufacturer. For energy flexible devices, it is assumed that they could also have no user interface at all, or that they are very constrained when it comes to computing power. It is assumed that a CEM always has a user interface. + +![Deployment_options](@site/static/img/communication-layer/deployment_options.png) + +There are three types of S2 connections between nodes possible: + +* **WAN-WAN**: A connection between two nodes deployed in a WAN. Connecting between them is straightforward and can be done based on URLs, based on DNS domain names. It is possible to rely on TLS certificates that can be validated thanks to a public key infrastructure. +* **WAN-LAN**: A connection between a LAN deployed node a WAN deployed node. Since there is almost always a firewall and/or NAT between these two, it is assumed that it is only possible to set up a connection from the LAN to the WAN; not the other way around. Connecting from the LAN node to the WAN node can be done based on a URL, and common TLS certificates can be used thanks to public key infrastructure. +* **LAN-LAN**: A connection between two LAN deployed nodes. It is assumed that in this situation we cannot rely an internet connection, making it impossible to rely on a public key infrastructure for TLS certificates. That is why for this type of connection self-signed TLS certificates are used. Connections are made based on hostnames that are resolved to IP-addresses using Multicast DNS (mDNS), since IP-addresses are not guaranteed to be stable. Discovering another node could be done using DNS Service Discovery (DNS-SD). + +## Pairing and unpairing from the perspective of the end user +The end user can take the initiative to *pair* a single CEM instance with a single RM instance. This process has to be started with one of the nodes. Which node this is depends on the deployment and implementation decisions of the node, but ideally it could be either one. The node however needs to have a user interface. We'll call the node that user uses to start the pairing process the *initiator*. We'll call the other node the *responder*. + +The first step of pairing is establishing a connection from the initiator node to the responder node. This can be done in several ways: +* Enter the responder node address manually at the initiator node. +* If the responder node is deployed in the WAN, the URL could be retrieved through a registry. The end user would have to select the type of node from a list of known node services in its region. +* If both nodes are deployed in the LAN however, nodes can be automatically be detected. The end user would have to select the node from a list of automatically discovered nodes. + +The second step is entering the pairing code of the responder node. This is a means for the end user to confirm that these two nodes are allowed to send control signals through S2 to each other. The pairing code can be obtained from the responder node. The pairing code is a (seemingly) random string of characters. This pairing code is typically displayed somewhere in the user interface of the other node. We recommend to use a dynamic token which expires after 5 minutes. However, if the node has a physical presence in the building and doesn't have user interface, there is also the option to have a static pairing code which can be printed on the device. + +Optionally, the initiator node can send a signal to the responder node to indicate that the end user has started the pairing process and has selected the responder node. This could trigger the user interface of the responder node to proactively show the pairing code (e.g. through a pop-up) to improve the user experience. + +Once the pairing code is known to the initiator node, the pairing process is started. It is established that both nodes are compatible and it is verified that the entered pairing code is correct. Pairing could either fail or succeed. + +If pairing is performed successfully, the CEM and RM instances should establish a connection with each other and communicate through S2. If the connection is interrupted, the instances will automatically try to reestablish the connection. + +Once a CEM is paired, the user has to possibility to command either of the nodes to *unpair*. After unpairing the CEM and RM instances can no longer communicate through S2 (unless the end user pairs them again). + +![Pairing_process_user](@site/static/img/communication-layer/pairing_process_user.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +participant "Initiator node" as i +participant "Initiator node UI" as iui +actor "End user" as e +participant "Responder node UI" as rui +participant "Responder node" as r + +e->iui: Provide identity of Responder node (e.g. URL) +rui->e: Retrieve pairing code +e->iui: Provide pairing code +i->r: Attempt pairing +r->i: Pairing result (success or failure) +iui->e: Pairing result (success or failure) +@enduml +``` +
+ +## The node and the endpoint + +Within this protocol we make a clear distinction between two types of identities: the one of the *endpoint* and the one of the *node* itself. + +An node is an instance of a CEM or a RM. It is either a specific energy management system or service, or the representative of a physical energy flexible device, such as a heat pump or a home battery. It is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. + +The endpoint is basically the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. + +## Used technology for pairing and communication + +An S2 connection basically consists of four steps: discovery, pairing, communication and unpairing. For these steps different types of technology are used. + +On of the main technologies the process relies on is HTTP REST. All interactions based on HTTP are formally described in OpenAPI specification files. [OpenAPI](https://swagger.io/specification/) is a formal language for specifying HTTP based API's. It can be used to generate reference documentation for developers, as well as stub code for many programming languages. + +### Discovery + +The first step is finding the responder node from the initiator node. In principle this is done based on the URL of the responder node. However, to improve user experience, two systems exist to find this URL in a more user friendly manner. For more details see [Discovery](#discovery). + +* If the responder node is deployed in the WAN, the end user can find the endpoint through the pairing endpoint registry. This would result in a list of vendors that offer nodes. +* If both nodes are deployed in the LAN however, the responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. This process can also be used when an node is deployed in the WAN, but the device also has a presence in the LAN. + +### Pairing + +The pairing process itself is completely based on HTTP REST. One node behaves as the HTTP server, and the other as the HTTP client. This process is described in an OpenAPI file. The process consists of multiple steps. If the pairing process is completed successfully, the nodes will agree on an access token. This token is used to initiate communication or to unpair. + +We'll refer to the endpoint that behaves as the HTTP server during the pairing process as the *pairing server*, and the client as the *pairing client*. + +Pairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) + +### Communication + +Communication is setting up the actual session, where S2 messages are being exchanged. + +The process always starts with HTTP based communication, but then is handed over to a protocol which supports a two-way messages based communication channel. Currently the only protocol that is being used is WebSockets, but there are plans to add other options in the future. The HTTP interface is also specified in an OpenAPI file, together with the unpairing process. + +We'll refer to the endpoint that behaves as the HTTP server during the communication process as the *communication server*, and the client as the *communication client*. + +It should be noted that pairing and communication are two separate HTTP interfaces, that don't have to be used in the same way. It could be that an node is an pairing client, but then becomes a communication server. This depends on the deployment of the nodes (see [Pairing details for different deployments](#pairing-details-for-different-deployments)). + +Communication interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) + +After the HTTP interaction a WebSocket is established (other transport protocols will be added in the future). The communication server is always the WebSocket server. This server must use the same TLS certificate as the HTTP server. + +### Unpairing + +Either node can take the initiative to unpair from the other node. This is done using the same HTTP OpenAPI specification and the same HTTP server and client as the communication. The details for unpairing differ depending if it is the communication server or if it is the communication client that initiates the unpairing process. + +Unpairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) + + +## Pairing details for different deployments + +As explained, the pairing process is based on HTTP REST calls. That means that for every pairing attempt, one node behaves as the HTTP server, and one HTTP node behaves as the pairing client. The logical solution would be to make the initiator node the HTTP client and the responder node the HTTP server. After all, it is the HTTP client that takes the initiative to contact the HTTP server. The HTTP server cannot take the initiative to contact the HTTP client. + +The objective is to have all nodes be able to be the initiator node, as well as the responder node. This is necessary to provide a consistent user experience. The end user might not be aware which node is deployed in the LAN or in the WAN, and then it might be confusing that, for example, his energy management system both provides pairing codes and asks for pairing codes. + +If every node must be able to be the initiator node in certain situations, and the responder node in other situations, and the easiest solution is to implement the initiator as HTTP client and the responder as HTTP server, you might come to the conclusion that every node needs to be able to behave both as an HTTP server and as an HTTP client. + +There are however two situations where this is not possible: + +* **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that it is not possible to approach a LAN HTTP server from a WAN client. This specifications offers two approaches to this problem: + * Accept this limitation and not allow the WAN node to be the initiator node. Pairing can only be performed when the LAN node is the initiator node and the WAN node is the responder node. Special care must be taken to explain this to the end user. + * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement the pairing HTTP server in the cloud, even though the node itself is in the WAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. +* **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTP server is far more memory intensive than an HTTP client, there is an option to implement a LAN RM instance purely as an HTTP client. A long-polling mechanism is available to indicate to the HTTP Server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTP server. In other words: in this specific situation the initiator node behaves as the HTTP server, and the responder node only has to be an HTTP client. + +![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) + + +# Formal specification and versioning (normative) + +This document serves as an overall specification of the S2 Connect protocol. However, where possible, the protocol has been specified in a formal specification language in order to minimize possible interpretation and allow tooling to assist the implementation of the specification. Since many details are better described in these formal specification files, they are not described in this document. Where the formal specification files and this document overlap, the formal specification file is leading. + +| Part of specification | Description | Specification format | Location | +| --- | --- | --- | --- | +| S2 Connect pairing API | HTTP based interaction to pair two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-pairing.yml) | +| S2 Connect connection API | HTTP based interaction set up a communication channel for S2 messages between two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-connection-init.yml) | +| S2 JSON message structure | The types of S2 messages that can be exchanges between nodes | JSON schema files | [Github](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema) | + +## Versioning of OpenAPI files +The S2 Connect pairing API and the S2 Connect connection API are formally defined as an OpenAPI file. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. The pairing API and the connection API share the same version number. + +The minor version is increased when backwards compatible changes are made. Be aware that we consider adding items to certain lists of enums (e.g. the list of supported hash functions) backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. + +The major version is increased when non-backwards compatible changes are made. + +The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTP server and HTTP clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must server all version on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as port of the URL (e.g. `["v1", "v2"]`). + +> TODO: Versioning of S2 JSON Schema's needs to be explained + +## Addressing endpoints (normative) +The URL of the pairing and connection API are used in the discovery process, pairing process and connection process, as wel as the basis for TLS certificates. + +For WAN deployed endpoints, the URL **must** be based on a DNS domain name. + +For LAN deployed endpoint, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instance within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. + +# Pairing process (normative) + +The pairing process is based on the trust relation that the end user has with both the CEM and the RM instances. That trust relation is out of scope for this specification and is up to CEM and RM providers to implement. + +For each pairing attempt, one endpoint must be the HTTP server, while the other is the HTTP client. The table below defines which endpoint is het HTTP server or client is which situation. + +| Initiator node | Responder node | Responder is exclusively RM? | Initiator HTTP role | Responder HTTP role | WAN pairing server | Remark | +| ----------------- | ----------------- | ---------------------------- | ------------------- | ------------------- | --------------------- | ------ | +| WAN | WAN | Doesn't matter | Client | Server | n/a | | +| WAN | LAN | Doesn't matter | Client | n/a | Server | The WAN pairing server for LAN deployment is optional | +| LAN | WAN | Doesn't matter | Client | Server | n/a | | +| LAN | LAN | Yes | Server | Client | n/a | Through long-polling. Decision to implement the server or the client is up to the RM endpoint. | +| LAN | LAN | Yes | Client | Server | n/a | Alternative for long-polling. Decision to implement the server or the client is up to the RM endpoint. | +| LAN | LAN | No | Client | Server | n/a | | + +> A LAN deployed RM implementation can choose if it implements the HTTP server, or that it implements the HTTP client and uses long-polling. This feature exists to accommodate RM implementations with constrained hardware. + +A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. An node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). + +## Discovery + +> TODO: This section still notes to be expanded + +In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing endpoint of the other node. In other words, the discovery process is a way to provide an node with the URL of another node which is needed to start the pairing process. Alternatively, it is always possible to initiate the pairing by manually providing the URL by the end user. + +> NOTE: the discovery process specification is work in progress and will be updated soon. + +### WAN-WAN +Both the S2 RM and CEM run in the cloud (for example communicating with the device via a manufacturer specific protocol). Discovery of the other node by lookup in a central registry. + +> NOTE: how the API of the registry will look like will be published soon + +### WAN-LAN + +A hybrid scenario where either the RM or CEM is deployed locally and the other in the cloud. Discovery of the cloud node by lookup in a central registry or DNS-SD in case the node is also present on the LAN. + +> NOTE: the DNS-SD service specification will be published soon + +### LAN-LAN +A LAN scenario where both RM and CEM are running on the same local network. Discovery through DNS-SD. + +> NOTE: the DNS-SD service specification will be published soon + +### DNS-SD based discovery +DNS-SD is used for automatically discover nodes from an node that is deployed in the LAN. This method can be used in three ways. + +* To discover another node that is deployed in the LAN, which is the responder node +* To advertise a [long polling endpoint](#long-polling) so other initiator nodes in the LAN could connect to this node +* To discover an node of which the RM is deployed in the WAN, but that also has a presence in the LAN. + +S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it is an HTTP based protocol. S2 Connect uses the following DNS-SD values: + +| DNS-SD property | Value for S2 | +| --- | --- | +| Service type | `_s2connect` | +| Protocol | `_tcp` | +| Port | No fixed port, decision is up to the implementation | +| Subtypes | `_cem` and `_rm` | +| Service name | Identical to the hostname (see [here](#addressing-endpoints-normative) for more details) + +An endpoint deployed in the LAN **should** publish it service through DNS-SD once it is ready for pairing, and until it shuts down. + +A device has a presence in the LAN (e.g. a heat pump which is connected to the internet via the end user's WiFi), but has its RM deployed in the WAN, can still use DNS-SD to advertise its endpoint to any local nodes (e.g. a physical energy management device containing an CEM). In this case the device doesn't have to offer any actual service in the LAN; it merely uses the TXT record to advertise its WAN endpoint URL. An arbitrary port may be specified. + +Two DNS-SD subtypes are used for endpoints. Subtypes can be used to filter services. + + * `_cem` is used when the endpoint contains one or more CEM node + * `_rm` is used when the endpoint contains one or more RM node + * `_cem` and `_rm` are both used when the endpoint contains both CEM and RM nodes + +S2 uses the following TXT records when registering for services. In the table below, M indicates a mandatory value and O indicates on optional value. + +| Record name | M/O | Description +| --- | --- | --- | +| `txtver` | M | Version of this specification of usage of the TXT record. **Must** be the literal value `1` for this version | +| `e_name` | O | The name of this endpoint (identical to the `name` property in the `EndpointDescription` object as defined in de OpenAPI specification) | +| `e_logoUrl` | O | The logoUrl of this endpoint (identical to the `logoUrl` property in the `EndpointDescription` object as defined in de OpenAPI specification) | +| `deployment` | M | **Must** be the literal value `LAN` or `WAN` (identical to the `logoUrl` property in the `EndpointDescription` object as defined in de OpenAPI specification) | +| `pairingUrl` | O | The base URL of the pairing API of this endpoint, excluding the version name but including the last slash (e.g. `https://hostname.local/pairing/`). If no value is provided, a `longpollingUrl` **must** be provided. +| `longpollingUrl` | O | The base URL of the pairing API of this endpoint on which the longpolling feature is implemented. The URL should be provided excluding the version name but including the last slash (e.g. `https://hostname.local/pairing/`). Only needs to be provided when longpolling is supported. Can only be provided if the value for `deployment` is equal to `LAN`. + +> Note: It is mandatory to provide a value for at least one of the properties `pairingUrl` and `longpollingUrl`. Providing both is also possible. + +The receiver of the service description **must** use the URL provided in the TXT records; not the hostname or IP-address and port associated with the service registry. + +> Note: You may have noticed that the full URL of the endpoint is used in the TXT records, even though the endpoint and port are already exposed by DNS-SD itself. This is done to avoid any problems with TLS certificates, which are pinned to a certain domain name. When using a WAN endpoint, the full URL needs to be specified as well, since no local service is actually being offered. + +> Scanning for endpoints could for example be done using the following [avahi](https://avahi.org/) command: +> +> `avahi-browse -r _s2connect._tcp` +> +> Registering an endpoint could for example be done using the folling avahi command: +> +> `avahi-publish-service -s "EVSE1038" _s2connect._tcp 443 "txtvers=1" "e_name=brand" "deployment=LAN" "pairingUrl=https://EVSE1038.local:443/pairing/" --sub _rm._sub._s2connect._tcp` + +## The pairing token, the node ID alias and the pairing code + +The pairing token is a random string of characters that is generated by the responder node. It is a secret which is transferred by the end user to the initiator node, and then is verified during the pairing process. Since there will be many cases where the end user has to manually type in the pairing token, the pairing token has to be short enough to make it easy for the end user to type in, but long enough to make it secure. + +The pairing token may consist out of lower case letters, upper case letters and numbers. The pairing token **must** be generated by a cryptographically secure pseudorandom number generator. The pairing token is typically dynamically generated when the user requests the pairing token at the user interface of the responder node. Dynamically generated pairing tokens **must** expire after a duration; five minutes is the recommended duration. However, energy flexible devices that do not have a user interface are allowed to have a static pairing token, that for example can be printed somewhere on the physical device. Static pairing tokens do not expire. Dynamic pairing tokens **must** contain at least 4 characters. Static paring tokens **must** contain at least 6 characters. Pairing tokens may be as long as the developer deems necessary. + +| Type of pairing token | Minimal length | Validity | Regular expression | +| --- | --- | --- | --- | +| Dynamic pairing token | 4 characters | Limited duration, 5 minutes is recommended | `^[0-9a-zA-Z]{4,}$` | +| Static pairing token | 6 characters | Indefinitely | `^[0-9a-zA-Z]{6,}$` | + +An endpoint can host multiple nodes. When attempting to pair a certain node, the endpoint needs to know exactly which of its nodes this pairing attempt is aimed at. Nodes are uniquely identified with their node ID. Since this node ID is a UUID, it is pretty long and cumbersome to type in. That is why an endpoint can assign its nodes a *node ID alias*. This is an identifier that is intended to be short, and only unique within the context of this particular endpoint. Node ID aliases could be assigned by the endpoint whenever new nodes are created, but also could be generated dynamically only when someone is attempting to pair to this node. This way, node ID aliases have a short live, and can be reused by other nodes at other moments. This allows to use shorter node ID aliases. Node ID aliases are a string of characters, which may include lower case letters, upper case letters and numbers. Node ID aliases are ideally as short as possible (at least one character), but should of course be long enough to allow the endpoint to uniquely identify an node. When an endpoint only contains one node, there is no need for a node ID alias. + +The **node ID alias** can be validated with the following regular expression: + +``` +^[0-9a-zA-Z]+$ +``` + +Although the pairing token and the node ID alias are two separate strings, which are treated completely differently in the pairing process, they are presented together to the user as one string: the *pairing code*. The pairing code is simply the node ID alias, followed by a dash ('-'), followed by the pairing token. When there is no node ID alias, the pairing code is simply identical to the pairing token. + +> Note: The node ID alias is not secret and is exchanged during a pairing attempt. The pairing token is secret. It is never exchanged; it is only used as input for the challenge response function. + +``` +When no node ID alias is used (i.e. the endpoint only contains one node): + [pairing code] = [pairing token] +When a node ID alias ID is used: + [pairing code] = [node ID alias]-[pairing token] +``` + +Alternatively, the **pairing code** can be validated with the following regular expression: + +``` +^([0-9a-zA-Z]+-)?[0-9a-zA-Z]{4,}$ +``` + +The pairing code allows us to transfer two pieces of information by only bothering the end user once. Due to its format the initiator node can easily extract the node ID alias and the pairing token from the pairing code by splitting the string at the dash. + + +## Challenge response process + +This protocol uses a two-way challenge response process to verify that both nodes have the same pairing token. For this process it doesn't matter which node has issued the pairing token and which node has the pairing token that was entered by the end user. The reason a two-way challenge response process is used to verify the pairing token is that it allows to establish trust without having to expose the pairing token. Both a challenge and a response are binary data, which are encoded using Base64. + +The challenge that is generated by the HTTP Client is called the `clientHmacChallenge`. The response to this challenge, generated by the HTTP Server, is called the `clientHmacChallengeResponse`. The challenge that is generated by the HTTP Server is called the `serverHmacChallenge`. The response to this challenge, generated by the HTTP Client, is called the `serverHmacChallengeResponse`. + +A challenge is a nonce; a random binary data. It **must** be generated by a cryptographically secure pseudorandom number generator and it **must** have a minimal length of 32 bytes. The response is calculated based on the function described below. Both the generator of the challenge and the receiver of the challenge calculate the response based several input parameters. Since both nodes should have the same input, both nodes should calculate the same response. The node that received the challenge sends it back to the node that generated the challenge. Now the node that generated the challenge simply has to check if the received response is identical to the expected response that he calculated himself. + +The algorithm to calculate the response is based on the HMAC (hash-based message authentication code) function. This function has a *key* and a *message* as arguments. Most programming languages have a function or library available that provides HMAC functions. + +The HMAC function itself uses a cryptographic hash function for its calculations. Since cryptographic hash functions might contain vulnerabilities, this protocol uses a simple selection mechanism for the cryptographic hash function. The HTTP client sends with the requestPairing HTTP request a list of supported hash functions. In the response the HTTP server indicates which hash function it has selected from this list. This function **must** be used for all response calculations during het pairing attempt. Currently there is only one hash function available (SHA256), but other options might be added in the future. + +It order to avoid man-in-the-middle attacks when using self-signed certificates, the SHA256 fingerprint of the TLS *server certificate* can also be used as input for calculating the response. + +> Note that the pairing token is encoded using Base64, so it must also be decoded using Base64 before it can be used in the challenge response function. + +The exact function to calculate the response depends on the deployment of the nodes. + +``` +When both nodes have a LAN deployment: + R = HMAC(C, T || F) + +When at least one node has a WAN deployment: + R = HMAC(C, T) +``` + +Where: +| Symbol | meaning | +| ------ | ------- | +| `R` | Response +| `HMAC` | HMAC function with the selected cryptographic hash function | +| `C` | Challenge | +| `T` | Pairing token | +| `F` | SHA256 fingerprint of the TLS server certificate of the HTTP server | +| `\|\|` | Concatenation | + + +## Pre-pairing interaction + +> This section is only applicable for LAN-LAN pairing + +> TODO: This section needs to be improved + +The user visits the S2ClientNodeUI and the S2ServerNode has been discovered (so the S2ServerNode base URL is known) by the the S2ClientNode per [discovery](#discovery) as specified above. The S2ClientNode does a preparePairing HTTP request to let the S2ServerNode know that there is an S2ClientNode that wants to pair. It is up to the S2ServerNode implementation to decide what to do with this signal. It can be used to display a pop-up with the pairing token in its UI to improve the user experience. It must be implemented by the client, but only when there is a clear distinction between the moment preparePairing is called and when requestPairing is called. When preparePairing is called, it is not guaranteed that a call to pairingRequest or cancelPreparePairing will follow so it is recommended to put a time-out on showing the pairing token in the S2ServerNodeUI. + +## Long-polling + +> This section is only applicable for LAN-LAN pairing + +The long-polling feature is intended to support endpoints that only want to implement an HTTP client, and not an HTTP server. Typically this is because the endpoint runs on constrained hardware. An endpoint is only allowed to only implement the client if it exclusively hosts nodes that have the RM role. + +Imagine we have endpoints, one only hosting a CEM node, and one only hosting a RM node. The RM runs on constrained hardware, and only implements the HTTP client. We have two situations: + +1. **The RM is the initiator node and the CEM is the responder node**: The CEM issues a pairing code, the end users enters the pairing code in the UI of the RM. The RM endpoint (HTTP client) then sends a normal HTTP request to the CEM endpoint (HTTP server) to initiate pairing. The normal pairing process can be used, and long-polling is not required. +2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTP server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTP client). + +For the second situation the long-polling feature can be used. It can be used by the initiator node (the HTTP server) to notify the responder node (the HTTP client) it wants to pair. + +> Informative: Long-polling is a technique that allows the server to send signals to the client without a significant delay, and without relying on additional technologies such as Websockets or Server-Sent Events. The common alternative is polling, where the client sends a request on a regular interval; let's say every 30 seconds. Polling creates a delay from the perspective of the server. If the server wants to send something to the client, it has to wait until the client contacts the server; which in the worst case 30 seconds. With long-polling the server doesn't immediately respond the the request (a hanging HTTP request). It responds immediately when the server wants the client to do something, or just before the request would time out. After receiving the response from the server the client immediately opens a new request to allow the server to send signals the client again. + +The long-polling feature fulfills the following functionality: +* Make the existence of the client known to server, together with the nodes IDs of the nodes that are represented by the client endpoint +* Send the `NodeDescription` and `EndpointDescription` of nodes represented by the client when requested by the server +* Send a prepare pairing signal or cancel prepare pairing from the server to the client for a particular node ID +* Send the signal from the server to the client to initiate pairing for a particular node ID +* Send an error message from the client to the server when pairing cannot be performed + +A client capable of long-polling **should** initiates long-polling when it encounters a endpoint through DNS-SD that indicates that is available for long-polling requests. When the endpoint represents zero nodes the client **cannot** attempt long-polling. When the endpoint advertisement itself, or only its long-polling indication disappears from DNS-SD the client **should** stop the long-polling process for that server. The client **must** also stop when it is no longer capable of pairing. + +The server **must** always respond within 25 seconds after receiving the request. The client **must** use a request time-out of at least 30 seconds. + +> TODO: Move the OpenAPI version selection process to its own section so we don't have to explain it every time + +The client starts the process by doing a POST request to the `/waitForPairing` path. For full normative details see the OpenAPI specification files. The request body contains a list of objects. The client **must** always provide an object for each node ID it represents. The items in the list have a mandatory property `clientNodeId` and optional parameters `clientNodeDescription`, `clientEndpointDescription`. The client should only provide values for these properties when requested by the server. The object also contains the optional property `errorMessage`, which only should be used when an error has occurred before pairing. + +When the server wants the client to immediately do a new request, it responds with status code 204. When it wants the client to do something, it responds with status 200 and a response body containing a list. This list contains an object only for node IDs represented by the client, that the server wants to do something with. This object contains the mandatory properties `clientNodeId` and `action`. The `action` property is an enumeration indicating an action the server wants to execute for a specific node. The possible action values are `sendNodeDescription`, `preparePairing`, `cancelPreparePairing` and `requestPairing`. + +The table below indicates how the client should respond to the requests of the server. Note that the server could send multiple actions (for different node IDs) in the same response. The server **cannot** provide multiple objects for the same node ID in one response. + +| Status code | Value `action` | Pairing code entered? | What should the client do | What should the client include in the next request body | +| --- | --- | --- | --- | --- | +| 204 | n/a | n/a | Only send a next request | Only the `clientNodeId` | +| 200 | `sendNodeDescription` | n/a | Only send the next request | The `clientNodeId`, `clientNodeDescription` and `clientEndpointDescription` | +| 200 | `preparePairing` | n/a | Prepare pairing for the mentioned node ID and send the next request | Only the `clientNodeId` | n/a | +| 200 | `cancelPreparePairing` | n/a | Cancel prepare pairing for the mentioned node ID and send the next request | Only the `clientNodeId` | +| 200 | `requestPairing` | Yes | Initiate the pairing for the mentioned node ID and send the next request | Only the `clientNodeId` | +| 200 | `requestPairing` | No | Initiate the pairing for the mentioned node ID and send the next request | Only the `clientNodeId`, and for the associated object provide an `errorMessage` with value `NoValidTokenOnPairingClient` | +| 400 | n/a | n/a | Stop long-polling until next time long-polling is advertised through DNS-SD | n/a | +| 401 | n/a | n/a | Stop long-polling, do not attempt long-polling with this node again | n/a | +| 500 | n/a | n/a | Wait before trying to send the next request | Only the `clientNodeId`| + +2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user inters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTP server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTP client). + +When the server sends the `requestPairing` action, the node on the client must already have issued a pairing token. If the node uses a dynamic pairing code, it could be the case that the pairing code has expired, or that no pairing code has been issued in the first place. In that case the client **must** perform a new request with an `errorMessage` containing the value `NoValidTokenOnPairingClient` in the object associated with the node ID of the node should have attempted to pair. + +The activity diagram below summarizes the complete long-polling process from teh perspective of the client. + +![image](@site/static/img/communication-layer/long-polling_activity_diagram.png) + +## Pairing interaction + +The pairing process itself consists of several HTTP interactions between client and server. The image below depicts a successful pairing process between two nodes. + +![image](@site/static/img/communication-layer/pairing_http_process.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +participant "HTTP Client" as Client +participant "HTTP Server" as Server + +'select version of pairing API +Client->Server++: 1. GET / (index containing pairing API versions) +Server-->Client: 2. Response status 200 +deactivate Server +Client->Client: 3. Decide pairing version + +'compatibility check +Client->Server: 4. POST /[version]/requestPairing +activate Server +Server->Server: 5. Calculate clientHmacChallengeResponse +Server-->Client: 6. Response status 200 +deactivate Server + +Client->Client: 7. HTTP Client checks clientHmacChallengeResponse + +Note over Client: HTTP Client now trusts HTTP Server + +Client->Client: 8. Calculate serverHmacChallengeResponse + +alt Pairing server is Communication Server + Client->Server: 9A. POST /[version]/requestConnectionDetails + activate Server + Server->Server: 10A. HTTP Server checks serverHmacChallengeResponse + Note over Server: HTTP Server now trusts HTTP Client + Server-->Client: 11A. Response status 200 + deactivate Server +else Pairing server is Communication Client + Client->Server: 9B. POST /[version]/postConnectionDetails + activate Server + Server->Server: 10B. HTTP Server checks serverHmacChallengeResponse + Note over Server: HTTP Server now trusts HTTP Client + Server-->Client: 11B. Response status 204 + deactivate Server +end + +Client->Server++: 12. POST /[version]/finalizePairing +Server-->Client: 13. Response status 204 +deactivate Server + +Note over Client, Server: Pairing finalized + +@enduml +``` +
+ +### 0. Precondition + +Before two node can be paired, the following preconditions must be met. + +1. The HTTP server and the HTTP client can only start with a pairing request when they are fully initialized and have all the details of the nodes it represents available. +2. The HTTP client must have the base URL of the pairing API (e.g. `https://hostname.local/pairing/`) +3. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. + +> Note: The initiator node could be the HTTP server or the HTTP client + +If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the pairing process. + +### 1. GET / (index containing pairing API versions) +Since the HTTP client does not know which major versions of the pairing API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | +| Store fingerprint of TLS certificate for later check | | + +If no checks fail the client **should** proceed to the next step. + +### 2. Response status 200 +The server responds with a list of implement major versions of the pairing API. It is formatted as a JSON array contains all the supported version of the pairing API (e.g. `["v1"]`). + +If the HTTP client does not support any of the provided versions, it means that the two endpoints are not compatible, and that pairing is not possible. + +### 3. Decide pairing version +From the provided list of major versions of the pairing API, the HTTP client must select one that is implement by the HTTP client itself (typically the highest supported version). + +### 4. POST /[version]/requestPairing +In the first POST request the client provides the server with same information about itself. The main purpose of this is to check if these two nodes are compatible. + +The client sends the following information (for full details see the OpenAPI specification file): + +| Information | Description | +| --- | --- | +| `clientNodeDescription` | Information about the node that wants to pair, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the initiator node | +| `clientEndpointDescription` | Information about the client endpoint. An important field is the deployment. | +| `nodeId` | The nodeID of the node that is being targeted (this filed can be omitted if the client only knows the `nodeIdAlias` or when the endpoint only represents one node). | +| `nodeIdAlias` | The nodeIdAlias of the node that is being targeted (this field can be omitted if the client only knows the `nodeId` or when the endpoint only represents one node) | +| `supportedCommunicationProtocols` | List of supported communications protocols of the client | +| `supportedS2MessageVersions` | List of supported S2 message versions by the client | +| `supportedHmacHashingAlgorithms` | List of supported hashing algorithms for the challenge response function (currently only `SHA256` is supported and **must** be present) | +| `clientHmacChallenge` | The challenge of the client for the challenge response process (see [Challenge response process](#challenge-response-process) | +| `forcePairing` | Indicate if the nodes must pair, even though they (currently) do not support the same S2 message versions (this could in the future be solved with a software update) | + +Be aware that the client may never provide a value for `nodeId` and `nodeIdAlias` at the same time. When the server endpoint only represents one node, both properties may be omitted. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | +| Check if same fingerprint is used as previous request | Pairing is failed, do not proceed with the pairing attempt | + +If no checks fail the client **should** proceed to the next step. + + +The server **must** perform the checks in the table below to make sure that it can proceed with this request. If one of these checks fail, the server should respond with an HTTP status 400 and a `PairingResponseErrorMessage`. The contents of the `additionalInfo` field is supposed the be helpful and up to the implementer. + +| Check | Type of `PairingResponseErrorMessage` when check fails | Can be ignored when `forcePairing` is true ?| +| --- | --- | --- | +| Is the request properly formatted and does it follow the schema? | `ParsingError` | No | +| Does it recognize the `nodeIdAlias`? | `NodeNotFound` | No | +| Are the endpoint and node ready for pairing? | `Other` | No | +| If no `nodeIdAlias` provided, does this endpoint indeed only represent one node? | `NoNodeIdProvided` | No | +| Does the targeted node have a different role than the initiator node (i.e. you cannot pair two RM's or two CEM's)? | `InvalidCombinationOfRoles` | No | +| Does the server accept any of the provided hashing algorithms for the challenge response process? | `IncompatibleHmacHashingAlgorithms` | No | +| Is there overlap between the communication protocols? | `IncompatibleCommunicationProtocols` | Yes | +| Is there overlap between the S2 message versions? | `IncompatibleS2MessageVersions` | Yes | +| If the targeted node on the HTTP server is the initiator node, did the end user provide a valid pairing token? | `NoValidPairingTokenOnPairingServer` | No | +| If the targeted node on the HTTP server is the responder node, does the node have a pairing token which has not expired? | `NoValidPairingTokenOnPairingServer` | No | + +> Note: If the node that is being paired is an RM which is already paired, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation must be unpaired. + +> Note: If the targeted node is already paired with the initiator node, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation is maintained. + +> Note: This is the only step where it is checked if the pairing code has expired. If the pairing token expires after this step, but during the pairing process, the pairing process will continue. A pairing attempt is limited to 15 seconds. + +If no checks fail the server **should** proceed to the next step. + +### 5. Calculate clientHmacChallengeResponse +The server selects an hashing algorithm for the challenge response function from the list that was provided by the client. This has to be a hashing algorithm that the server considers secure. The server calculates a response to the provided `clientHmacChallenge`. For details see [Challenge response process](#challenge-response-process). + +### 6. Response status 200 +In order to formulate a response, the server **must** generate a `pairingAttemptId`. This is an identifier that **must** be generated by a cryptographically secure pseudorandom number generator and encoded using Base64. This identifier is used to keep track of all the HTTP interactions during the pairing attempt, and **must** be provided by the HTTP client as a header with all subsequent interactions. A pairing attempt **must** be completed within 15 seconds, or else the server **must** assume the pairing attempt has failed. + +The server responds with the following information (for full details see the OpenAPI specification file): + +| Information | Description | +| --- | --- | +| `pairingAttemptId` | The generated identifier for this pairing attempt | +| `serverNodeDescription` | Information about the node that is being targeted, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the responder node | +| `serverEndpointDescription` | Information about the server endpoint. An important field is the deployment. | +| `selectedHmacHashingAlgorithm` | The hashing algorithm for the challenge response function as selected in step 2 | +| `clientHmacChallengeResponse` | The response to the challenge provided by the HTTP client as calculated in step 2 | +| `serverHmacChallenge` | The challenge created by the HTTP server for the challenge response process (see [Challenge response process](#challenge-response-process).) | + +The client **must** perform the following checks of this data. + +| Check | How to proceed if check fails | +| --- | --- | +| Can the contents of the response be parsed? | Do not proceed with the pairing attempt | +| Is the response formatted according to the schema? | call `/finalizePairing` where `success` is `false` if `pairingAttemptId` is available | +| Is the role of the node at the server compatible? | call `/finalizePairing` where `success` is `false` | + +If no checks fail the server **should** proceed to the next step. + +### 7. HTTP Client checks clientHmacChallengeResponse +The HTTP client checks the `clientHmacChallengeResponse` provided by the HTTP server in step 6. It does that by calculating the response itself, and checking if the results is identical to the `clientHmacChallengeResponse`. + +If the result is identical, the client **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt. It **must** attempt to inform the HTTP server of this by doing an HTTP request to `finalizePairing` where the value of `success` must be `false`. + +Note that in case of a local server, the TLS certificate fingerprint is part of the challenge. So if the challenge succeeds, the certificate fingerprint is correct, and the certificate can be trusted. The client **must** pin this certificate, and trust this certificate for future use. + + +### 8. Calculate serverHmacChallengeResponse +The HTTP client calculates a response to the provided `serverHmacChallenge` using the hashing algorithm as indicated in the `selectedHmacHashingAlgorithm`. For details see [Challenge response process](#challenge-response-process). + +From hereon the process branches into two scenario's, depending on if the HTTP client will be the communication client or the communication server. See [Mapping the CEM and RM to communication server or client](#mapping-the-cem-and-rm-to-communication-server-or-client) for which node will perform which role for communication. + +If the HTTP server will be the communication *server* steps 9A, 10A and 11A **should** follow. If the HTTP server will be the communications *client* steps 9B, 10B en 11B **should** follow. + +### 9A. POST /[version]/requestConnectionDetails +> Note: The `pairingAttemptId` must be provided through a header for this HTTP request + +The HTTP client makes a request for the connection details. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 8. + +If the `pairingAttemptId` is not recognized by the server (or has expired), the server **must** respond with status code 401. + +If the request was not understood by the server for any other reason, the server **must** respond with status 400. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | +| Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | + +If no checks fail the client **should** proceed to the next step. + + +### 10A. HTTP Server checks serverHmacChallengeResponse +The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 9A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. + +If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 or step 4. + +### 11A. Response status 200 +The server **must** generates an access token for the HTTP client. The access token is random binary data and **must** be generated by a cryptographically secure pseudorandom number generator and **must** have a minimum length of 32 bytes. It is encoded using Base64. The access token **cannot** be used by the initiator node until the pairing process is completed. + +The server responds with two pieces of information: + +| Information | Description | +| --- | --- | +| `initiateConnectionUrl` | The base URL for the connection process (does not include the version number) | +| `accessToken` | The access token that was generated for this node | + +If the response is understood and properly formatted, the HTTP client **should** proceed to the next step. Otherwise the HTTP client **must** stop the pairing attempt. It **must** attempt to inform the HTTP server of this by doing an HTTP request to `finalizePairing` where the value of `success` must be `false`. + +### 9B. POST /[version]/postConnectionDetails +> Note: The `pairingAttemptId` must be provided through a header for this HTTP request + +The HTTP sends the connection details to the HTTP server. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 8. + +In this case the pairing server will become the communication client. Once the pairing server becomes the communication client, it does not know what the certificate that the communication server will use. That is why it needs to provide it using the property `certificateFingerprint`. This property is a map, where the key of the map is the hashing algorithm used to generate the fingerprint, and the value is the fingerprint itself. The hashing function `SHA256` and the related fingerprint **must** always be provided. + +| Information | Description | +| --- | --- | +| `serverHmacChallengeResponse` | The response for the challenge response process | +| `initiateConnectionUrl` | The base URI for the connection process (does not include the version number) | +| `accessToken` | The access token that was generated for this node | +| `certificateFingerprint` | A map with the fingerprint of the CA (root) certificate. The key of the map is the name of the hashing algorithm used to generate the fingerprint, the value is the fingerprint itself. The key `SHA256` must always be provided. | + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | +| Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | + +If no checks fail the client **should** proceed to the next step. + +The server **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| `pairingAttemptId` is recognized | Respond with status code 401 | +| Request could not be parsed correctly | Respond with status code 400 | + +If no checks fail the server **should** proceed to the next step. + +### 10B. HTTP Server checks serverHmacChallengeResponse +The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 9A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. + +If the result is identical, the server **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 or step 4. + +### 11B. Response status 204 +The server confirms it has accepted the response and received the connection details by responding with HTTP status 204. + +### 12. POST /[version]/finalizePairing +> Note: The `pairingAttemptId` must be provided through a header for this HTTP request + +If all interaction has been successful until this point, the HTTP client **must** do a request to finalize the pairing attempt. The provided value for `success` **must** be `true`. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | +| Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | + +If no checks fail the client **should** proceed to the next step. + +The server **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| The `pairingAttemptId` is correctly recognized | respond with status code 401 | +| The request is not understood for any other reason | respond with status code 400 | + +If no checks fail the server **should** proceed to the next step. + +Receiving a `/finalizePairing` request marks the completion of the pairing attempt for the HTTP server. If the HTTP server issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTP client. + +### 13. Response status 204 +To confirm the successful completion of the pairing attempt, the HTTP server responds to the client with HTTP status code 204. This response marks the completion of the pairing attempt for the HTTP client. If the HTTP client issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTP client. + +If the HTTP server was using a self-signed TLS certificate, the HTTP client can now store the self-signed root certificate. The client **must** check that this is the CA certificate that is used for all future interaction with this endpoint. The HTTP server is allowed to use a new self-signed server certificate, as long as it is signed by the self-signed CA certificate that was used during the pairing process. + +### Interruption of the process +A pairing attempt has a maximum duration of 15 seconds. That means that once a `pairingAttemptId` has been issued, this `pairingAttemptId` cannot be used after 15 seconds since it was issued. From the perspective of the HTTP server, any pairing attempt that is not completed in 15 seconds (with success or not) is considered a failed attempt. From the perspective of the HTTP client, if the server does not respond within 15 seconds since it received the `pairingAttemptId`, it must consider the pairing attempt as failed. If the HTTP client wants to make another attempt, it should start again at step 1 or step 4. + +### Invalid interactions +If the server receives a wrong HTTP request (e.g. `/postConnectionDetails` while it was expecting `/requestConnectionDetails`) or when it receives the requests in the wrong order (e.g. `/finalizePairing` with `success` = `true` before calling `/requestConnectionDetails`) it **must** respond with a status 400 and consider the pairing attempt as failed. The only exception is receiving the same request twice. + +# S2 Connection (normative) + +After two nodes have been paired, the nodes exchange S2 messages over a secure connection. + +The following mechanism **must** be used to initiate a secure connection between two nodes. Client authentication is based on a one-time use communication token that needs to be renewed every time a new S2 session is created. The communication client will always attempt to set up an S2 connecting with the communication server when there is no connection. For more details see [Reconnection strategy](#reconnection-strategy). + +## Mapping the CEM and RM to communication server or client + +The CEM and RM roles defined by the S2 protocol are distinct from the server and client roles of the pairing process. The following rules apply to determine whether the RM or CEM acts as a client or server for the communication initialization. + +* If a connection is set up between a WAN node and a LAN node, the WAN node must act as a communication server, and the local node must act as a communication client. +* If a connection is set up between two nodes that are similarly deployed (i.e. both in WAN, or both in LAN), the CEM must act as a communication server, and the RM must act as a communication client. + +There are four scenarios for CEM and RM deployment, and applying the rules above yields the following: + +| CEM deployment | RM deployment | CEM acts as | RM acts as | +|----------------|---------------|-------------|------------| +| WAN | WAN | Communication server | Communication client | +| WAN | LAN | Communication server | Communication client | +| LAN | WAN | Communication client | Communication server | +| LAN | LAN | communication server | Communication client | + +> Note: A device developed solely for use as an RM in a LAN setup will never function as a communication server. + +## Connection initiation + +During the pairing process an `accessToken` is generated by the node which will be the communication server and sent to the node that will be the communication client. This `accessToken` can be used by the communication client to set up a session with the communication server for exchanging S2 messages. The `accessToken` does not expire, but it can only be used (successfully) once to set up a connection. Each time a new connection is made, the `accessToken` will be renewed. The communication server will generate a new `accessToken` and sends it to the communication client. Since this `accessToken` is the only means to connect two nodes once they are paired, the connection initiation process makes sure that both nodes confirm that they have successfully persisted the new `accessToken` before invalidating the old `accessToken`. + +![connection initiation](@site/static/img/communication-layer/connection-initiation.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +participant "HTTP Client" as Client +participant "HTTP Server" as Server + +Client->Server++: 1. GET / (index containing pairing API versions) +Server-->Client: 2. Response status 200 +deactivate Server +Client->Client: 3. Decide pairing version + +Client->Server++: 4. POST /[version]/initiateConnection +Server->Server: 5. Generate new pending accessToken +Server-->Client--: 6. Response status 200 +Client->Client: 7. Store pending accessToken +Client->Server++: 8. POST /[version]/confirmAccessToken +Server->Server: 9. Activate new accessToken for this node ID +Server-->Client--: 10. Response status 200 +Client -> Client : 11. Remove old accessToken +@enduml +``` + +
+ +### 0. Precondition + +Before an node can initiate a connection, it needs three things. + +1. The HTTP server and the HTTP client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. +2. The HTTP client must have the base URL of the connection API (e.g. `https://hostname.local/connection/`) +3. The two nodes must have been paired successfully and must have an accessToken for this pairing + +If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the connection process. + +### 1. GET / (index containing communication API versions) +Since the HTTP client does not know which major versions of the communication API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Do not proceed with connection, try again later | +| If self-signed TLS certificate, check if server is local | Do not proceed with connection, try again later | + +If no checks fail the client **should** proceed to the next step. + +### 2. Response status 200 +The server responds with a list of implement major versions of the pairing API. It is formatted as a JSON array contains all the supported version of the pairing API (e.g. `["v1"]`). + +If the HTTP client does not support any of the provided versions, it means that the two endpoints are not compatible, and that connection is not possible. + +### 3. Decide communication API version +From the provided list of major versions of the communication API, the HTTP client must select one that is implement by the HTTP client itself (typically the highest supported version). + +### 4. POST /[version]/initiateConnection + +Since there are situations in which the client cannot know for sure which `accessToken` the communication server uses for this pairing, the communication client must keep a persisted list of `accessTokens` (which will typically contain only one `accessToken`). + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Initiation is failed, do not proceed with the initiation attempt | +| If self-signed TLS certificate, check if server is local | Initiation is failed, do not proceed with the initiation attempt | +| Check if TLS certificate is pinned | Initiation is failed, do not proceed with the initiation attempt | + +If no checks fail the client **should** proceed to the next step. + +The client sends the following information (for full details see the OpenAPI specification file). In addition, the `accessToken` is sent through a header. + +| Information | Description | +| --- | --- | +| `clientNodeId` | The node ID of the communications client that wants to connect to the server. | +| `serverNodeId` | The node ID of the communications server that the client wants to connect to. | +| `clientNodeDescription` | Information about the node, such as brand, logo and type. This only needs to be provided if the communication client wants to update this information, otherwise the communication server will assumer the stored information is still valid. | +| `clientEndpointDescription` | Information about the client endpoint. This only needs to be provided if the communication client wants to update this information, otherwise the communication server will assumer the stored information is still valid. | +| `supportedCommunicationProtocols` | List of supported communications protocols of the client | +| `supportedS2MessageVersions` | List of supported S2 message versions by the client | + +The server **must** perform the checks in the table below to make sure that it can proceed with this request. If one of these checks fail, the server should respond with an HTTP status 400 and a `CommunicationDetailsErrorMessage` or with HTTP status 401. The contents of the `additionalInfo` field of the `CommunicationDetailsErrorMessage` is supposed the be helpful and up to the implementer. + +| Check | Response | What should the client do with this message? | +| --- | --- | --- | +| Is the request properly formatted and does it follow the schema? | `CommunicationDetailsErrorMessage` with errorMessage `ParsingError` | Retry later | +| Was this node ID paired with this node, but was it unpaired? | `CommunicationDetailsErrorMessage` with errorMessage `NoLongerPaired` | Do not retry, inform end user | +| Is this `clientNodeId` paired with the `serverNodeId`? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | +| Is the `serverNodeId` known? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | +| Is this the correct `accessToken` for this node ID? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | +| Is there overlap between the communication protocols? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleCommunicationProtocols` | Retry later | +| Is there overlap between the S2 message versions? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleS2MessageVersions` | Retry later | +| Are the endpoint and node ready for pairing? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | + +### 5. Generate new pending `accessToken` + +For each paired node the server saves an active `accessToken`. In addition to that, the server also has a list for pending `accessToken`s, that were generated but not yet confirmed by the client. This list contains entries, each consisting of an `accessToken`, the node IDs of the client and server nodes and a timestamp. + +The server generates a new `accessToken` and saves this together with the node ID and the current time as in entry in the list of pending tokens. The `accessToken` **must** be generated by a cryptographically secure pseudorandom number generator. + +### 6. Response status 200 + +In the request the client supplied a list of supported communication protocols and S2 messages versions. The server must select one of the options that were provided by the client. + +The server sends the following information (for full details see the OpenAPI specification file). + +| Information | Description | +| --- | --- | +| `selectedCommunicationProtocol` | The communication protocol that was selected by the server | +| `selectedS2MessageVersion` | The S2 message version that was selected by the server | +| `accessToken` | The newly generated pending `accessToken` | +| `serverNodeDescription` | Information about the node at the server, such as brand, logo and type. This only needs to be provided if the communication server wants to update this information, otherwise the communication client will assumer the stored information is still valid. | +| `serverEndpointDescription` | Information about the server endpoint. This only needs to be provided if the communication server wants to update this information, otherwise the communication client will assumer the stored information is still valid. | + +The client **must** perform the checks in the table below to make sure that it can proceed with this request. + +| Check | What should the client do? | +| --- | --- | +| Is the request properly formatted and does it follow the schema? | Do not proceed and try again later with step 1 | +| Was the selected S2 message version offered in the request? | Do not proceed and try again later with step 1 | +| Was the selected communication protocol offered in the request? | Do not proceed and try again later with step 1 | + +### 7. Store pending accessToken +It client adds the pending `accessToken` to its list of `accessTokens`, but does not yet remove the old one. If the client is not able to persist the pending `accessToken` (e.g. because the storage device or the DBMS is not available), the client does not proceed with the process. Once the client is able to persist `accessTokens` again, it can retry to set up a connection starting with step 1. + +### 8. POST /[version]/confirmAccessToken +The client confirms to the server that it has successfully persisted the pending `accessToken`. The **pending** `accessToken` is provided through the header of the request. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Do not proceed with connection, try again later | +| If self-signed TLS certificate, check if server is local | Do not proceed with connection, try again later | +| Check if TLS certificate is pinned | Do not proceed with connection, try again later | + +If no checks fail the client **should** proceed. + +### 9. Activate new `accessToken` for this node ID + +If the provided `accessToken` is in the list pending `accessToken`s, and the token was generated not more than **15 seconds** ago, the server now makes the pending `accessToken` the active `accessToken` for this pairing of nodes (thereby invalidating the old `accessToken`). Also, the entry is removed from the list of pending `accessToken`s. + +If the provided `accessToken` is not in the list of pending `accessTokens`s, the server must not accept the connection and respond with status 401. The client can try again later starting at step 1. + +If the server is not able to active the new `accessToken` (e.g. because the storage device or the DBMS is not available), the server must not accept the connection and responds with an error code 500. The client can try again later starting at step 1. + +### 10. Response status 200 + +The communication server sends the details and credentials to open a socket for communicating the S2 messages. The exact contents of this message depend on the selected communication protocol. In any case it will be a JSON object containing the field `communicationProtocol`. The presence on other fields will depend on the value of the `communicationProtocol` field. + +If the response is not understood by the communication client, the client **should** retry later. + +### 11. Remove old accessToken + +Step 10 functions as a confirmation to the communication client that the communication server has activated the new `accessToken` for this pairing. The old `accessToken` cannot be used anymore, so the communication client must remove the old `accessToken` from the list of `accessToken`s. + +### Interruption of the process +Once the communication server has generated a new pending `accessToken`, it must be confirmed within 15 seconds by the communication client. If this doesn't happen, a client will have to start the process from step 1 (or step 4) again. + +If the communication client doesn't receive a response to confirming the new `accessToken` (step 10), it does not know if the server has activated the new `accessToken`, or if the old `accessToken` is still in place. It now has (at least) two `accessToken`s in its list, and does not know for certain which one is activate at the communication server. It should try all the accessTokens sequentially. If it finds an `accessToken` that is accepted by the communication server, it can remove the other `accessTokens`. + +## WebSocket based communication + +This section specifies how to use WebSocket Secure as the S2-over-TCP/IP application layer protocol. + +The WebSocket client **must** run on the communication client and the WebSocket server on the communication server. + +The choice for a WebSocket as application layer communication protocol has the advantage that the session concept is intrinsically introduced with the communication protocol. All S2 communication happens in the context of a (stateful) S2 session which is catered for by the WebSocket session. So, the S2 session matches the WebSocket session. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Websocket connection failed, do not proceed with the connection attempt | +| If self-signed TLS certificate, check if server is local | Websocket connection failed, do not proceed with the connection attempt | +| Check if TLS certificate is pinned | Websocket connection failed, do not proceed with the connection attempt | + +If no checks fail the client **should** proceed to the next step. + + +### Authentication +For each S2 WebSocket session the client **must** authenticate itself using the commToken in the authorization header of the websocket connection request, following [RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://datatracker.ietf.org/doc/html/rfc6750). + +### Encrypted connection (WSS) + +Communication over the WebSocket endpoint **must** be encrypted following [RFC 6455 The WebSocket Protocol](https://datatracker.ietf.org/doc/html/rfc6455). S2 **MUST NOT** be sent over unencrypted channels. Therefore a wss connection (the URL starting with wss://) must be used. + +### Compression + +The WebSocket Protocol ([RFC6455](https://datatracker.ietf.org/doc/html/rfc6455)) has an extension for compression: [**RFC 7692**](https://datatracker.ietf.org/doc/html/rfc7692.html) implementing so called per-message-deflate compression. https://datatracker.ietf.org/doc/html/rfc7692 + +RFC 7692 is widely supported by WebSocket libraries and and we are exchanging JSON plain text messages, it is expected to save a large amount of data. Therefore, implementations of S2 WebSockets **SHOULD** support RFC 7692 and **SHOULD** enable it whenever possible. + +### Keepalive & heartbeat (ping / pong) + +WebSockets by default have a **keepalive** and a **heartbeat mechanism**. Keepalive is designed to keep the connection open while heartbeat is designed to check the latency and check the connection is still working. This means that periodically a ping frame is sent to the server (endpoint) and in response a pong frame is sent. + +In order to reduce network traffic, S2 WebSocket implementations **SHOULD** not send ping frames more often than every 50 seconds. Ping & pong frames are control frames and **MAY** include payload of maximum 125 bytes. + +[https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2%5C%5C) + + + +### Termination + +An S2 session can be terminated in different ways: + +* In case an node unexpectedly becomes unavailable, the WebSocket connection **CAN** timeout. This will cause an S2 session to be terminated. More details about the timeout can be found [in the heartbeat section](#keepalive--heartbeat-ping--pong) +* an node **CAN** terminate the S2 session by sending the S2 terminate message, including an optional earliest time that the session can be restored. The other node can take this into account in planning and (in the case of a client) deciding when to attempt to reconnect. +* After two nodes have unpaired, the S2 WebSocket connection **MUST** be terminated immediately. + +### Reconnection strategy + +Once an S2 session is terminated it cannot be resumed and if further communication is required, a new session needs to be started. an S2 client node may try to establish a WebSocket connection. + +An exponential back-off strategy **SHOULD** be used, increasing the time between reconnection attempts at every failed attempt. If a reconnection time was included in any termination, S2 client nodes are permitted to make an attempt to reconnect before this time. At the specified time the delay between reconnection attempts **SHOULD** be reset. + +## Communication - JSON messages + +The S2 standard has been encoded into a JSON schema specification, for details see: [S2 JSON](https://github.com/flexiblepower/s2-ws-json) + +### State of communication + +![State of Communication](@site/static/img/communication-layer/state-of-communication.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml + +hide empty description +[*] --> WebSocketConnected + +WebSocketConnected : ResourceManagerDetails, PowerMeasurement, \n PowerForecast can be exchanged + +WebSocketConnected --> ControlTypeActivated: Activate ControlType +ControlTypeActivated --> WebSocketConnected: Deactivate ControlType + +ControlTypeActivated: In addition to messages in\nthe "Initialized" state,\nControlType specific message\ncan now be exchanged as well + +WebSocketConnected --> WebSocketDisconnected: Termination of Session +ControlTypeActivated --> WebSocketDisconnected: Termination of Session + +WebSocketDisconnected --> [*] +@enduml +``` +
+ +| State | Messages that can be sent by CEM /received by RM | Messages that can be sent by RM / received by CEM | +| --- | --- | --- | +| WebSocket Connected | SelectControlType
SessionRequest
ReceptionStatus | ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType PEBC activated | PEBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | PEBC.EnergyConstraint
PEBC.PowerConstraint
RevokeObject InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType PPBC activated | PPBC.EndInterruptionInstruction
PPBC.ScheduleInstruction
PPBC.StartInterruptionInstruction
SelectControlType
SessionRequest
ReceptionStatus | PPBC.PowerProfileDefinition
PPBC.PowerPorfileStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType OMBC activated | OMBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | OMBC.Status
OMBC.SystemDescription
OMBC.TimerStatus RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType FRBC activated | FRBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | FRBC.ActuatorStatus
FRBC.FillLevelTargetProfile
FRBC.LeakageBehaviour
FRBC.StorageStatus
FRBC.SystemDescription
FRBC.UsageForecast
FRBC.TimerStatus
RevokeObject
InstructionStatusUpdate ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType DDBC activated | DDBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | DDBC.ActuatorStatus
DDBC.AverageDemandRateForecast
DDBC.SystemDescription
DDBC.TimerStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement PowerForecast
SessionRequest
ReceptionStatus | + +# Unpairing process (normative) + +Unpairing can be initiated by either node, and **should** only be done when instructed by the end user. The node that did not take the initiative to unpair **should** try to inform the end user that the node is no longer paired. + +## Unpairing by the communication client + +If the communication client takes the initiative to unpair, it first **should** close the communication session regarding this pairing (if there is one). Then it **must** call the `/[version]/unpair` HTTP API endpoint of the server using its `accessToken` (after discovering the API version at the server, the same way as steps 1 to 3 as mentioned in [Connection initiation](#connection-initiation)). Upon receiving the unpair call from a client, the server node **must** remove all security information related to this pairing. Then the client **must** remove all security information of the communication server related to this pairing. + +## Unpairing by the communication server + +If the server takes the initiative to unpair, first it **must** remove all security information related to client node. After that, if there is an active S2 session, it **should** send an S2 [SessionRequest](/model-reference/Common/SessionRequest/) message with type [RECONNECT](/model-reference/Common/SessionRequestType/) to the client. The next `/[version]/initiateConnection` HTTP API request **must** fail with the `No longer paired` response to let the client know that it is not paired anymore. + +Client and server **can** keep other (non-security) information for, for example, user experience purposes. + +# Security (normative) + +> TODO: This section needs to be expanded to explain measures against ddos + +## Brute-force protection +To prevent brute-force pairing request, the server **MUST** implement rate limiting on the requestPairing endpoint. It is up to the server implementation to define the type of rate limiting. + + +## TLS Certificates + +All HTTP and WebSocket communication uses TCP over TLS with server certificates. The server certificates **MUST** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. + +There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local domain name. This is also the only situation where self-signed certificates are allowed. + +The following image shows the difference. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. + +![image.png](../../static/img/communication-layer/certificate-chains.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +struct PublicRootCA +struct PublicIntermediateCA +struct PublicServerCertificate + +PublicRootCA --> PublicIntermediateCA +PublicIntermediateCA --> PublicServerCertificate + + +struct SelfSignedCA +struct LocalServerCertificate + +SelfSignedCA --> SelfSignedCA +SelfSignedCA --> LocalServerCertificate +@enduml +``` +
+ + +### Trusting a self-signed root certificate + +The self-signed root certificate is by default not trusted. However during the pairing phase, the server with the self-signed root certificate will share the fingerprint of the certificate during the pairing phase as part of the challenge. This will enable the client to verify the self-signed root certificate, and create trust. In this case, the [pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code) **MUST** include the first 9 bytes, encodes as 12 base64 encoded characters, of the fingerprint of this self-signed CA certificate and the client **MUST** check this fingerprint. From this moment on, the client will store the complete fingerprint of the self-signed root certificate, and use it to verify the server certificate for all future connections. + +Note that the `preparePairing` and `cancelPreparePairing` endpoints can be called before the pairing has happened. So in the case the server is running on a LAN (and thus uses self-signed certificates), the client can skip the certificate validation steps on those endpoint. This means that the HTTP client **must** be configured to accept self-signed certificates during the pairing process. Since the pairing process consists of several HTTP requests, the HTTP client **must** check that for every request the same self-signed certificate is used by the HTTP server. If this is not the case, the HTTP client **cannot** proceed with the request. + + +### Updating the certificates + +A server can update its certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. + +If the server is in local-local mode, and uses a self-signed CA certificate, the CA certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server. If the used crypto for the the CA certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its server certificate at least once every 6 months. + + +## Cipher suites + +Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. diff --git a/website/s2c_versions.json b/website/s2c_versions.json new file mode 100644 index 0000000..29f9986 --- /dev/null +++ b/website/s2c_versions.json @@ -0,0 +1,3 @@ +[ + "1.0-beta2" +] From 02f2a077dc80eb2edc81ca72f94c550616ecfe38 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 7 May 2026 13:12:09 +0200 Subject: [PATCH 06/67] Add Client Redirect for old path of S2 connect specification and version dropdown menu --- website/docusaurus.config.ts | 25 ++++++++++++++++--- website/package-lock.json | 25 +++++++++++++++++++ website/package.json | 1 + .../DocsVersionDropdownNavbarItem.tsx | 20 +++++++++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 website/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 5a74910..3dd4c98 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -65,6 +65,14 @@ const config: Config = { routeBasePath: "s2-connect", breadcrumbs: false, sidebarPath: false + }], + ['@docusaurus/plugin-client-redirects', { + redirects: [ + { + to: '/s2-connect/discovery-pairing-authentication/', + from: ['/docs/communication-layer/discovery-pairing-authentication/'], + }, + ], }] ], @@ -91,11 +99,19 @@ const config: Config = { position: 'left', label: 'Reference', }, + // { + // to: 's2-connect/discovery-pairing-authentication', + // activeBaseRegex: 's2-connect', + // position: 'left', + // label: 'S2 Connect', + // }, { - to: 's2-connect/discovery-pairing-authentication', - activeBaseRegex: 's2-connect', - position: 'left', - label: 'S2 Connect', + type: 'docsVersionDropdown', + position: 'right', + // props passed to the themed navbar item component - our themed override will read `showOn` + versions: ['current', '1.0-beta2'], + docsPluginId: 's2c', + // only show the dropdown when path starts with this prefix }, { href: 'https://github.com/flexiblepower/s2-documentation', @@ -144,6 +160,7 @@ const config: Config = { } satisfies Preset.ThemeConfig, scripts: [ + { src: '/js/pathClass.js', defer: true }, ], }; diff --git a/website/package-lock.json b/website/package-lock.json index ef7d933..72d769d 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@docusaurus/core": "3.10.1", "@docusaurus/faster": "3.10.1", + "@docusaurus/plugin-client-redirects": "3.10.1", "@docusaurus/preset-classic": "3.10.1", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", @@ -3650,6 +3651,30 @@ "react-dom": "*" } }, + "node_modules/@docusaurus/plugin-client-redirects": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.10.1.tgz", + "integrity": "sha512-LHgd+YDvkhfOHMAE6XtUng3DQNzVM765RqVRrMJgHtzAvfopQhY6ieprqjxDVBdv21cLma6I0jHr+YCZH8fL9A==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.1", + "@docusaurus/logger": "3.10.1", + "@docusaurus/utils": "3.10.1", + "@docusaurus/utils-common": "3.10.1", + "@docusaurus/utils-validation": "3.10.1", + "eta": "^2.2.0", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/@docusaurus/plugin-content-blog": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.1.tgz", diff --git a/website/package.json b/website/package.json index bd3d8d2..8e2cbb6 100644 --- a/website/package.json +++ b/website/package.json @@ -17,6 +17,7 @@ "dependencies": { "@docusaurus/core": "3.10.1", "@docusaurus/faster": "3.10.1", + "@docusaurus/plugin-client-redirects": "3.10.1", "@docusaurus/preset-classic": "3.10.1", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", diff --git a/website/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx b/website/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx new file mode 100644 index 0000000..9f2bf19 --- /dev/null +++ b/website/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx @@ -0,0 +1,20 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import DocsVersionDropdownNavbarItem from '@theme-original/NavbarItem/DocsVersionDropdownNavbarItem'; +import {useActiveDocContext} from '@docusaurus/plugin-content-docs/client'; + +export default function DocsVersionDropdownNavbarItemWrapper(props) { + // (CUSTOM) Hide version dropdown on non-versioned pages + const activeDocContext = useActiveDocContext(props.docsPluginId); + if (!activeDocContext.activeDoc) { + return null; + } + + return ; +} \ No newline at end of file From b3f5002454340c570f06233c68e6391cf0c98410 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 7 May 2026 14:00:29 +0200 Subject: [PATCH 07/67] Described per node rate limit for pairing attempts --- website/s2-connect/discovery-pairing-authentication.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index c23b78d..e51e47c 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -694,6 +694,8 @@ If no checks fail the server **should** proceed to the next step. ### 5. Calculate clientHmacChallengeResponse The server selects an hashing algorithm for the challenge response function from the list that was provided by the client. This has to be a hashing algorithm that the server considers secure. The server calculates a response to the provided `clientHmacChallenge`. For details see [Challenge response process](#challenge-response-process). +To mitigate brute-force attacks, the server **must** enforce a mandatory delay of one second before sending its response to the client (step 6). For any given node at the server, pairing attempts **must** be handled sequentially, such that each second only one pairing attempt can be processed for a node. Pairing attempts targeting different nodes **may** be processed in parallel. This way, a server representing multiple nodes is not globally limited to one pairing attempt per second, but instead enforces the one-second rate limit independently per node. + ### 6. Response status 200 In order to formulate a response, the server **must** generate a `pairingAttemptId`. This is an identifier that **must** be generated by a cryptographically secure pseudorandom number generator and encoded using Base64. This identifier is used to keep track of all the HTTP interactions during the pairing attempt, and **must** be provided by the HTTP client as a header with all subsequent interactions. A pairing attempt **must** be completed within 15 seconds, or else the server **must** assume the pairing attempt has failed. From 0072a0c4e221067ca4c2afdab7d1f578ee6dc91a Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Fri, 8 May 2026 11:56:12 +0200 Subject: [PATCH 08/67] Moved API version selection to its own section --- .../discovery-pairing-authentication.md | 222 +++++++++--------- .../api_version_selection.png | Bin 0 -> 5752 bytes .../connection-initiation.png | Bin 15217 -> 11495 bytes .../pairing_http_process.png | Bin 33134 -> 28944 bytes 4 files changed, 108 insertions(+), 114 deletions(-) create mode 100644 website/static/img/communication-layer/api_version_selection.png diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index e51e47c..91360c8 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -296,7 +296,7 @@ This document serves as an overall specification of the S2 Connect protocol. How | S2 JSON message structure | The types of S2 messages that can be exchanges between nodes | JSON schema files | [Github](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema) | ## Versioning of OpenAPI files -The S2 Connect pairing API and the S2 Connect connection API are formally defined as an OpenAPI file. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. The pairing API and the connection API share the same version number. +The S2 Connect pairing API and the S2 Connect connection API are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. The pairing API and the connection initiation API share the same version number. The minor version is increased when backwards compatible changes are made. Be aware that we consider adding items to certain lists of enums (e.g. the list of supported hash functions) backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. @@ -306,13 +306,59 @@ The major version of the API is embedded in the base URL of the API as `/v[major > TODO: Versioning of S2 JSON Schema's needs to be explained -## Addressing endpoints (normative) +## Addressing endpoints The URL of the pairing and connection API are used in the discovery process, pairing process and connection process, as wel as the basis for TLS certificates. For WAN deployed endpoints, the URL **must** be based on a DNS domain name. For LAN deployed endpoint, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instance within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. +## Selecting the version of the pairing or connection initiation API +As explained in the section [Versioning of OpenAPI files](#version) the pairing server or connection initiation server can implement multiple versions of the API specification in parallel. As a result, the client **must** always first determine which version of the API it will use, before it can start interacting with the API. + +The image below depicts the interactions between client and server for the process to determine the API version that will be used. + +![image](@site/static/img/communication-layer/api_version_selection.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +participant "HTTP Client" as Client +participant "HTTP Server" as Server + +Client->Server++: 1. GET / (index containing pairing API versions) +Server-->Client: 2. Response status 200 +deactivate Server +Client->Client: 3. Decide pairing version + +@enduml +``` +
+ +### 1. GET / (index containing pairing API versions) +Since the HTTP client does not know which major versions of the pairing API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | +| Store fingerprint of TLS certificate for later check | | + +If no checks fail the client **should** proceed to the next step. + +### 2. Response status 200 +The server responds with a list of implement major versions of the pairing API. It is formatted as a JSON array contains all the supported version of the pairing API (e.g. `["v1"]`). + +If the HTTP client does not support any of the provided versions, it means that the two endpoints are not compatible, and that further interaction is not possible. + +### 3. Decide pairing version +From the provided list of major versions of the pairing API, the HTTP client **must** select one that is implement by the HTTP client itself (typically the highest supported version). The client **should** select the most recent version. + + # Pairing process (normative) The pairing process is based on the trust relation that the end user has with both the CEM and the RM instances. That trust relation is out of scope for this specification and is up to CEM and RM providers to implement. @@ -483,7 +529,6 @@ Where: | `F` | SHA256 fingerprint of the TLS server certificate of the HTTP server | | `\|\|` | Concatenation | - ## Pre-pairing interaction > This section is only applicable for LAN-LAN pairing @@ -560,43 +605,37 @@ The pairing process itself consists of several HTTP interactions between client participant "HTTP Client" as Client participant "HTTP Server" as Server -'select version of pairing API -Client->Server++: 1. GET / (index containing pairing API versions) -Server-->Client: 2. Response status 200 -deactivate Server -Client->Client: 3. Decide pairing version - 'compatibility check -Client->Server: 4. POST /[version]/requestPairing +Client->Server: 1. POST /[version]/requestPairing activate Server -Server->Server: 5. Calculate clientHmacChallengeResponse -Server-->Client: 6. Response status 200 +Server->Server: 2. Calculate clientHmacChallengeResponse +Server-->Client: 3. Response status 200 deactivate Server -Client->Client: 7. HTTP Client checks clientHmacChallengeResponse +Client->Client: 4. HTTP Client checks clientHmacChallengeResponse Note over Client: HTTP Client now trusts HTTP Server -Client->Client: 8. Calculate serverHmacChallengeResponse +Client->Client: 5. Calculate serverHmacChallengeResponse alt Pairing server is Communication Server - Client->Server: 9A. POST /[version]/requestConnectionDetails + Client->Server: 6A. POST /[version]/requestConnectionDetails activate Server - Server->Server: 10A. HTTP Server checks serverHmacChallengeResponse + Server->Server: 7A. HTTP Server checks serverHmacChallengeResponse Note over Server: HTTP Server now trusts HTTP Client - Server-->Client: 11A. Response status 200 + Server-->Client: 8A. Response status 200 deactivate Server else Pairing server is Communication Client - Client->Server: 9B. POST /[version]/postConnectionDetails + Client->Server: 6B. POST /[version]/postConnectionDetails activate Server - Server->Server: 10B. HTTP Server checks serverHmacChallengeResponse + Server->Server: 7B. HTTP Server checks serverHmacChallengeResponse Note over Server: HTTP Server now trusts HTTP Client - Server-->Client: 11B. Response status 204 + Server-->Client: 8B. Response status 204 deactivate Server end -Client->Server++: 12. POST /[version]/finalizePairing -Server-->Client: 13. Response status 204 +Client->Server++: 9. POST /[version]/finalizePairing +Server-->Client: 10. Response status 204 deactivate Server Note over Client, Server: Pairing finalized @@ -611,34 +650,14 @@ Before two node can be paired, the following preconditions must be met. 1. The HTTP server and the HTTP client can only start with a pairing request when they are fully initialized and have all the details of the nodes it represents available. 2. The HTTP client must have the base URL of the pairing API (e.g. `https://hostname.local/pairing/`) -3. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. +3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api)) +4. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. > Note: The initiator node could be the HTTP server or the HTTP client -If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the pairing process. - -### 1. GET / (index containing pairing API versions) -Since the HTTP client does not know which major versions of the pairing API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | -| Store fingerprint of TLS certificate for later check | | - -If no checks fail the client **should** proceed to the next step. - -### 2. Response status 200 -The server responds with a list of implement major versions of the pairing API. It is formatted as a JSON array contains all the supported version of the pairing API (e.g. `["v1"]`). - -If the HTTP client does not support any of the provided versions, it means that the two endpoints are not compatible, and that pairing is not possible. - -### 3. Decide pairing version -From the provided list of major versions of the pairing API, the HTTP client must select one that is implement by the HTTP client itself (typically the highest supported version). +If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the pairing process. -### 4. POST /[version]/requestPairing +### 1. POST /[version]/requestPairing In the first POST request the client provides the server with same information about itself. The main purpose of this is to check if these two nodes are compatible. The client sends the following information (for full details see the OpenAPI specification file): @@ -691,12 +710,12 @@ The server **must** perform the checks in the table below to make sure that it c If no checks fail the server **should** proceed to the next step. -### 5. Calculate clientHmacChallengeResponse +### 2. Calculate clientHmacChallengeResponse The server selects an hashing algorithm for the challenge response function from the list that was provided by the client. This has to be a hashing algorithm that the server considers secure. The server calculates a response to the provided `clientHmacChallenge`. For details see [Challenge response process](#challenge-response-process). -To mitigate brute-force attacks, the server **must** enforce a mandatory delay of one second before sending its response to the client (step 6). For any given node at the server, pairing attempts **must** be handled sequentially, such that each second only one pairing attempt can be processed for a node. Pairing attempts targeting different nodes **may** be processed in parallel. This way, a server representing multiple nodes is not globally limited to one pairing attempt per second, but instead enforces the one-second rate limit independently per node. +To mitigate brute-force attacks, the server **must** enforce a mandatory delay of one second before sending its response to the client (step 3). For any given node at the server, pairing attempts **must** be handled sequentially, such that each second only one pairing attempt can be processed for a node. Pairing attempts targeting different nodes **may** be processed in parallel. This way, a server representing multiple nodes is not globally limited to one pairing attempt per second, but instead enforces the one-second rate limit independently per node. -### 6. Response status 200 +### 3. Response status 200 In order to formulate a response, the server **must** generate a `pairingAttemptId`. This is an identifier that **must** be generated by a cryptographically secure pseudorandom number generator and encoded using Base64. This identifier is used to keep track of all the HTTP interactions during the pairing attempt, and **must** be provided by the HTTP client as a header with all subsequent interactions. A pairing attempt **must** be completed within 15 seconds, or else the server **must** assume the pairing attempt has failed. The server responds with the following information (for full details see the OpenAPI specification file): @@ -720,25 +739,25 @@ The client **must** perform the following checks of this data. If no checks fail the server **should** proceed to the next step. -### 7. HTTP Client checks clientHmacChallengeResponse -The HTTP client checks the `clientHmacChallengeResponse` provided by the HTTP server in step 6. It does that by calculating the response itself, and checking if the results is identical to the `clientHmacChallengeResponse`. +### 4. HTTP Client checks clientHmacChallengeResponse +The HTTP client checks the `clientHmacChallengeResponse` provided by the HTTP server in step 3. It does that by calculating the response itself, and checking if the results is identical to the `clientHmacChallengeResponse`. If the result is identical, the client **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt. It **must** attempt to inform the HTTP server of this by doing an HTTP request to `finalizePairing` where the value of `success` must be `false`. Note that in case of a local server, the TLS certificate fingerprint is part of the challenge. So if the challenge succeeds, the certificate fingerprint is correct, and the certificate can be trusted. The client **must** pin this certificate, and trust this certificate for future use. -### 8. Calculate serverHmacChallengeResponse +### 5. Calculate serverHmacChallengeResponse The HTTP client calculates a response to the provided `serverHmacChallenge` using the hashing algorithm as indicated in the `selectedHmacHashingAlgorithm`. For details see [Challenge response process](#challenge-response-process). From hereon the process branches into two scenario's, depending on if the HTTP client will be the communication client or the communication server. See [Mapping the CEM and RM to communication server or client](#mapping-the-cem-and-rm-to-communication-server-or-client) for which node will perform which role for communication. -If the HTTP server will be the communication *server* steps 9A, 10A and 11A **should** follow. If the HTTP server will be the communications *client* steps 9B, 10B en 11B **should** follow. +If the HTTP server will be the communication *server* steps 6A, 7A and 8A **should** follow. If the HTTP server will be the communications *client* steps 6B, 7B en 8B **should** follow. -### 9A. POST /[version]/requestConnectionDetails +### 6A. POST /[version]/requestConnectionDetails > Note: The `pairingAttemptId` must be provided through a header for this HTTP request -The HTTP client makes a request for the connection details. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 8. +The HTTP client makes a request for the connection details. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 5. If the `pairingAttemptId` is not recognized by the server (or has expired), the server **must** respond with status code 401. @@ -755,12 +774,12 @@ The client **must** perform the following checks during this request: If no checks fail the client **should** proceed to the next step. -### 10A. HTTP Server checks serverHmacChallengeResponse -The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 9A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. +### 7A. HTTP Server checks serverHmacChallengeResponse +The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. -If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 or step 4. +If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). -### 11A. Response status 200 +### 8A. Response status 200 The server **must** generates an access token for the HTTP client. The access token is random binary data and **must** be generated by a cryptographically secure pseudorandom number generator and **must** have a minimum length of 32 bytes. It is encoded using Base64. The access token **cannot** be used by the initiator node until the pairing process is completed. The server responds with two pieces of information: @@ -772,10 +791,10 @@ The server responds with two pieces of information: If the response is understood and properly formatted, the HTTP client **should** proceed to the next step. Otherwise the HTTP client **must** stop the pairing attempt. It **must** attempt to inform the HTTP server of this by doing an HTTP request to `finalizePairing` where the value of `success` must be `false`. -### 9B. POST /[version]/postConnectionDetails +### 6B. POST /[version]/postConnectionDetails > Note: The `pairingAttemptId` must be provided through a header for this HTTP request -The HTTP sends the connection details to the HTTP server. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 8. +The HTTP sends the connection details to the HTTP server. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 5. In this case the pairing server will become the communication client. Once the pairing server becomes the communication client, it does not know what the certificate that the communication server will use. That is why it needs to provide it using the property `certificateFingerprint`. This property is a map, where the key of the map is the hashing algorithm used to generate the fingerprint, and the value is the fingerprint itself. The hashing function `SHA256` and the related fingerprint **must** always be provided. @@ -805,15 +824,15 @@ The server **must** perform the following checks during this request: If no checks fail the server **should** proceed to the next step. -### 10B. HTTP Server checks serverHmacChallengeResponse -The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 9A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. +### 7B. HTTP Server checks serverHmacChallengeResponse +The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. -If the result is identical, the server **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 or step 4. +If the result is identical, the server **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). -### 11B. Response status 204 +### 8B. Response status 204 The server confirms it has accepted the response and received the connection details by responding with HTTP status 204. -### 12. POST /[version]/finalizePairing +### 9. POST /[version]/finalizePairing > Note: The `pairingAttemptId` must be provided through a header for this HTTP request If all interaction has been successful until this point, the HTTP client **must** do a request to finalize the pairing attempt. The provided value for `success` **must** be `true`. @@ -839,13 +858,13 @@ If no checks fail the server **should** proceed to the next step. Receiving a `/finalizePairing` request marks the completion of the pairing attempt for the HTTP server. If the HTTP server issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTP client. -### 13. Response status 204 +### 10. Response status 204 To confirm the successful completion of the pairing attempt, the HTTP server responds to the client with HTTP status code 204. This response marks the completion of the pairing attempt for the HTTP client. If the HTTP client issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTP client. If the HTTP server was using a self-signed TLS certificate, the HTTP client can now store the self-signed root certificate. The client **must** check that this is the CA certificate that is used for all future interaction with this endpoint. The HTTP server is allowed to use a new self-signed server certificate, as long as it is signed by the self-signed CA certificate that was used during the pairing process. ### Interruption of the process -A pairing attempt has a maximum duration of 15 seconds. That means that once a `pairingAttemptId` has been issued, this `pairingAttemptId` cannot be used after 15 seconds since it was issued. From the perspective of the HTTP server, any pairing attempt that is not completed in 15 seconds (with success or not) is considered a failed attempt. From the perspective of the HTTP client, if the server does not respond within 15 seconds since it received the `pairingAttemptId`, it must consider the pairing attempt as failed. If the HTTP client wants to make another attempt, it should start again at step 1 or step 4. +A pairing attempt has a maximum duration of 15 seconds. That means that once a `pairingAttemptId` has been issued, this `pairingAttemptId` cannot be used after 15 seconds since it was issued. From the perspective of the HTTP server, any pairing attempt that is not completed in 15 seconds (with success or not) is considered a failed attempt. From the perspective of the HTTP client, if the server does not respond within 15 seconds since it received the `pairingAttemptId`, it must consider the pairing attempt as failed. If the HTTP client wants to make another attempt, it should start again at step 1 (starting with the API version selection process is also allowed). ### Invalid interactions If the server receives a wrong HTTP request (e.g. `/postConnectionDetails` while it was expecting `/requestConnectionDetails`) or when it receives the requests in the wrong order (e.g. `/finalizePairing` with `success` = `true` before calling `/requestConnectionDetails`) it **must** respond with a status 400 and consider the pairing attempt as failed. The only exception is receiving the same request twice. @@ -888,19 +907,14 @@ During the pairing process an `accessToken` is generated by the node which will participant "HTTP Client" as Client participant "HTTP Server" as Server -Client->Server++: 1. GET / (index containing pairing API versions) -Server-->Client: 2. Response status 200 -deactivate Server -Client->Client: 3. Decide pairing version - -Client->Server++: 4. POST /[version]/initiateConnection -Server->Server: 5. Generate new pending accessToken -Server-->Client--: 6. Response status 200 -Client->Client: 7. Store pending accessToken -Client->Server++: 8. POST /[version]/confirmAccessToken -Server->Server: 9. Activate new accessToken for this node ID -Server-->Client--: 10. Response status 200 -Client -> Client : 11. Remove old accessToken +Client->Server++: 1. POST /[version]/initiateConnection +Server->Server: 2. Generate new pending accessToken +Server-->Client--: 3. Response status 200 +Client->Client: 4. Store pending accessToken +Client->Server++: 5. POST /[version]/confirmAccessToken +Server->Server: 6. Activate new accessToken for this node ID +Server-->Client--: 7. Response status 200 +Client -> Client : 8. Remove old accessToken @enduml ``` @@ -912,32 +926,12 @@ Before an node can initiate a connection, it needs three things. 1. The HTTP server and the HTTP client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. 2. The HTTP client must have the base URL of the connection API (e.g. `https://hostname.local/connection/`) -3. The two nodes must have been paired successfully and must have an accessToken for this pairing +3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api)) +4. The two nodes must have been paired successfully and must have an accessToken for this pairing If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the connection process. -### 1. GET / (index containing communication API versions) -Since the HTTP client does not know which major versions of the communication API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Do not proceed with connection, try again later | -| If self-signed TLS certificate, check if server is local | Do not proceed with connection, try again later | - -If no checks fail the client **should** proceed to the next step. - -### 2. Response status 200 -The server responds with a list of implement major versions of the pairing API. It is formatted as a JSON array contains all the supported version of the pairing API (e.g. `["v1"]`). - -If the HTTP client does not support any of the provided versions, it means that the two endpoints are not compatible, and that connection is not possible. - -### 3. Decide communication API version -From the provided list of major versions of the communication API, the HTTP client must select one that is implement by the HTTP client itself (typically the highest supported version). - -### 4. POST /[version]/initiateConnection - +### 1. POST /[version]/initiateConnection Since there are situations in which the client cannot know for sure which `accessToken` the communication server uses for this pairing, the communication client must keep a persisted list of `accessTokens` (which will typically contain only one `accessToken`). The client **must** perform the following checks during this request: @@ -974,13 +968,13 @@ The server **must** perform the checks in the table below to make sure that it c | Is there overlap between the S2 message versions? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleS2MessageVersions` | Retry later | | Are the endpoint and node ready for pairing? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | -### 5. Generate new pending `accessToken` +### 2. Generate new pending `accessToken` For each paired node the server saves an active `accessToken`. In addition to that, the server also has a list for pending `accessToken`s, that were generated but not yet confirmed by the client. This list contains entries, each consisting of an `accessToken`, the node IDs of the client and server nodes and a timestamp. The server generates a new `accessToken` and saves this together with the node ID and the current time as in entry in the list of pending tokens. The `accessToken` **must** be generated by a cryptographically secure pseudorandom number generator. -### 6. Response status 200 +### 3. Response status 200 In the request the client supplied a list of supported communication protocols and S2 messages versions. The server must select one of the options that were provided by the client. @@ -1002,10 +996,10 @@ The client **must** perform the checks in the table below to make sure that it c | Was the selected S2 message version offered in the request? | Do not proceed and try again later with step 1 | | Was the selected communication protocol offered in the request? | Do not proceed and try again later with step 1 | -### 7. Store pending accessToken +### 4. Store pending accessToken It client adds the pending `accessToken` to its list of `accessTokens`, but does not yet remove the old one. If the client is not able to persist the pending `accessToken` (e.g. because the storage device or the DBMS is not available), the client does not proceed with the process. Once the client is able to persist `accessTokens` again, it can retry to set up a connection starting with step 1. -### 8. POST /[version]/confirmAccessToken +### 5. POST /[version]/confirmAccessToken The client confirms to the server that it has successfully persisted the pending `accessToken`. The **pending** `accessToken` is provided through the header of the request. The client **must** perform the following checks during this request: @@ -1018,7 +1012,7 @@ The client **must** perform the following checks during this request: If no checks fail the client **should** proceed. -### 9. Activate new `accessToken` for this node ID +### 6. Activate new `accessToken` for this node ID If the provided `accessToken` is in the list pending `accessToken`s, and the token was generated not more than **15 seconds** ago, the server now makes the pending `accessToken` the active `accessToken` for this pairing of nodes (thereby invalidating the old `accessToken`). Also, the entry is removed from the list of pending `accessToken`s. @@ -1026,20 +1020,20 @@ If the provided `accessToken` is not in the list of pending `accessTokens`s, the If the server is not able to active the new `accessToken` (e.g. because the storage device or the DBMS is not available), the server must not accept the connection and responds with an error code 500. The client can try again later starting at step 1. -### 10. Response status 200 +### 7. Response status 200 The communication server sends the details and credentials to open a socket for communicating the S2 messages. The exact contents of this message depend on the selected communication protocol. In any case it will be a JSON object containing the field `communicationProtocol`. The presence on other fields will depend on the value of the `communicationProtocol` field. If the response is not understood by the communication client, the client **should** retry later. -### 11. Remove old accessToken +### 8. Remove old accessToken -Step 10 functions as a confirmation to the communication client that the communication server has activated the new `accessToken` for this pairing. The old `accessToken` cannot be used anymore, so the communication client must remove the old `accessToken` from the list of `accessToken`s. +Step 7 functions as a confirmation to the communication client that the communication server has activated the new `accessToken` for this pairing. The old `accessToken` cannot be used anymore, so the communication client must remove the old `accessToken` from the list of `accessToken`s. ### Interruption of the process -Once the communication server has generated a new pending `accessToken`, it must be confirmed within 15 seconds by the communication client. If this doesn't happen, a client will have to start the process from step 1 (or step 4) again. +Once the communication server has generated a new pending `accessToken`, it must be confirmed within 15 seconds by the communication client. If this doesn't happen, a client will have to start the process from step 1. -If the communication client doesn't receive a response to confirming the new `accessToken` (step 10), it does not know if the server has activated the new `accessToken`, or if the old `accessToken` is still in place. It now has (at least) two `accessToken`s in its list, and does not know for certain which one is activate at the communication server. It should try all the accessTokens sequentially. If it finds an `accessToken` that is accepted by the communication server, it can remove the other `accessTokens`. +If the communication client doesn't receive a response to confirming the new `accessToken` (step 7), it does not know if the server has activated the new `accessToken`, or if the old `accessToken` is still in place. It now has (at least) two `accessToken`s in its list, and does not know for certain which one is activate at the communication server. It should try all the accessTokens sequentially. If it finds an `accessToken` that is accepted by the communication server, it can remove the other `accessTokens`. ## WebSocket based communication diff --git a/website/static/img/communication-layer/api_version_selection.png b/website/static/img/communication-layer/api_version_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..212fd6b8dc26d811f26fb7fca1c7421e93cb1462 GIT binary patch literal 5752 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 DDg7&Y literal 0 HcmV?d00001 diff --git a/website/static/img/communication-layer/connection-initiation.png b/website/static/img/communication-layer/connection-initiation.png index 3192ccc4ad8b30c5c5a00b82f4e38562a5b336ad..1ee1b6db3e0c513d0929ddcb06876c111bd79fa3 100644 GIT binary patch literal 11495 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 E1F=|E1^@s6 From c932c59692ecd7427fa8dc3d48ad6cf3ad13bcf0 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 12 May 2026 10:30:09 +0200 Subject: [PATCH 09/67] Added specification for the WAN pairing endpoint registry --- .../discovery-pairing-authentication.md | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 91360c8..65624b0 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -287,16 +287,17 @@ There are however two situations where this is not possible: # Formal specification and versioning (normative) -This document serves as an overall specification of the S2 Connect protocol. However, where possible, the protocol has been specified in a formal specification language in order to minimize possible interpretation and allow tooling to assist the implementation of the specification. Since many details are better described in these formal specification files, they are not described in this document. Where the formal specification files and this document overlap, the formal specification file is leading. +This document serves as an overall specification of the S2 Connect protocol. However, where possible, the protocol has been specified in a formal specification language in order to minimize possibilities for different interpretations and allow tooling to assist the implementation of the specification. Since many details are better described in these formal specification files, they are not described in this document. **Where the formal specification files and this document overlap, the formal specification file is leading.** | Part of specification | Description | Specification format | Location | | --- | --- | --- | --- | | S2 Connect pairing API | HTTP based interaction to pair two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-pairing.yml) | | S2 Connect connection API | HTTP based interaction set up a communication channel for S2 messages between two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-connection-init.yml) | +| S2 Connect WAN pairing endpoint registry API | HTTP based interface to query the registry | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-wan-endpoint-registry.yml) | | S2 JSON message structure | The types of S2 messages that can be exchanges between nodes | JSON schema files | [Github](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema) | ## Versioning of OpenAPI files -The S2 Connect pairing API and the S2 Connect connection API are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. The pairing API and the connection initiation API share the same version number. +The S2 Connect pairing API, the S2 Connect connection initiation API and the S2 Connect WAN pairing endpoint registry are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. All S2 Connect OpenAPI files share the same version number. The minor version is increased when backwards compatible changes are made. Be aware that we consider adding items to certain lists of enums (e.g. the list of supported hash functions) backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. @@ -309,12 +310,12 @@ The major version of the API is embedded in the base URL of the API as `/v[major ## Addressing endpoints The URL of the pairing and connection API are used in the discovery process, pairing process and connection process, as wel as the basis for TLS certificates. -For WAN deployed endpoints, the URL **must** be based on a DNS domain name. +For **WAN** deployed endpoints, the URL **must** be based on a DNS domain name. -For LAN deployed endpoint, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instance within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. +For **LAN** deployed endpoints, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instance within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. -## Selecting the version of the pairing or connection initiation API -As explained in the section [Versioning of OpenAPI files](#version) the pairing server or connection initiation server can implement multiple versions of the API specification in parallel. As a result, the client **must** always first determine which version of the API it will use, before it can start interacting with the API. +## Selecting the version of REST APIs +As explained in the section [Versioning of OpenAPI files](#version) the pairing server, the connection initiation server and the WAN pairing endpoint registry can implement multiple versions of the API specification in parallel. As a result, the client **must** always first determine which version of the API it will use, before it can start interacting with the API. The image below depicts the interactions between client and server for the process to determine the API version that will be used. @@ -380,33 +381,48 @@ A CEM can be paired with multiple RM's a the same time. A RM can only be paired ## Discovery -> TODO: This section still notes to be expanded +In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing endpoint of the other node. In other words, the discovery process is a way to provide an node with the URL of another node which is needed to start the pairing process. Alternatively, it **must** always possible to initiate the pairing by manually providing the URL by the end user. -In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing endpoint of the other node. In other words, the discovery process is a way to provide an node with the URL of another node which is needed to start the pairing process. Alternatively, it is always possible to initiate the pairing by manually providing the URL by the end user. +There are two mechanisms for discovery: For discovering WAN endpoints there is a central online registry. For discovering endpoints within the same LAN, DNS-SD is used. DNS-SD can also be used to discovery WAN endpoints of devices that have a presence in the LAN (typically an energy smart appliance which hosts the RM in the cloud). -> NOTE: the discovery process specification is work in progress and will be updated soon. +### WAN pairing endpoint registry -### WAN-WAN -Both the S2 RM and CEM run in the cloud (for example communicating with the device via a manufacturer specific protocol). Discovery of the other node by lookup in a central registry. +> Note: At this point the registry is specified, but not yet publicly available -> NOTE: how the API of the registry will look like will be published soon +The task of the registry is to facilitate a more user friendly way to determine the URL of the WAN pairing endpoint. Owners of an S2 Connect WAN pairing endpoint can register their endpoint at the registry. The user interface of a CEM or RM could show a list of relevant endpoints to the user (e.g. in a list or drop down menu) with details that would be easily recognizable to the end user (e.g. name and icon). By querying the registry the user interface can always show an up-to-date list of endpoints. The registry contains filtering functionality to filter endpoints that are relevant in the context. -### WAN-LAN +The registry uses the same versioning mechanism as the other S2 Connect OpenAPI files. See [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis) to see how the client can select the version of the API to use. -A hybrid scenario where either the RM or CEM is deployed locally and the other in the cloud. Discovery of the cloud node by lookup in a central registry or DNS-SD in case the node is also present on the LAN. +The registry contains the following information for each endpoint. For full normative details see the OpenAPI specification files. -> NOTE: the DNS-SD service specification will be published soon - -### LAN-LAN -A LAN scenario where both RM and CEM are running on the same local network. Discovery through DNS-SD. - -> NOTE: the DNS-SD service specification will be published soon +| Property | Description | +| --- | --- | +| `id` | Unique UUID identifier for the record | +| `name` | User facing name of the endpoint | +| `description` | User facing description of the endpoint| +| `icon32` | 32 by 32 pixel- icon of the endpoint | +| `icon128` | 128 by 128 pixels icon of the endpoint | +| `icon512` | 512 by 512 pixels icon of the endpoint | +| `pairingApiUrl` | The URL of the pairing API of the endpoint | +| `regions` | Array of regions in which this endpoint operates, as defined by the ISO 3166-1 alpha-2 country code | +| `status` | Status of the endpoint, can either be `testing` or `public` | +| `cem` | Boolean indicating if the endpoint represents CEM nodes | +| `rm` | Boolean indicating if the endpoint represents RM nodes | + +In order to filter out the relevant endpoint records the API supports the following filters: + +* Region in which the endpoint claims to be active +* Status, which can be `testing` or `public` (when no value provided, the default value will be `public`) +* Whether the endpoint contains CEM nodes +* Whether the endpoint contains RM nodes + +In addition, the number of responses can be limited. An offset can also be provided in order to split the results over multiple requests. ### DNS-SD based discovery DNS-SD is used for automatically discover nodes from an node that is deployed in the LAN. This method can be used in three ways. * To discover another node that is deployed in the LAN, which is the responder node -* To advertise a [long polling endpoint](#long-polling) so other initiator nodes in the LAN could connect to this node +* To advertise a [long polling URL](#long-polling) so other initiator nodes in the LAN could connect to this node * To discover an node of which the RM is deployed in the WAN, but that also has a presence in the LAN. S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it is an HTTP based protocol. S2 Connect uses the following DNS-SD values: @@ -650,7 +666,7 @@ Before two node can be paired, the following preconditions must be met. 1. The HTTP server and the HTTP client can only start with a pairing request when they are fully initialized and have all the details of the nodes it represents available. 2. The HTTP client must have the base URL of the pairing API (e.g. `https://hostname.local/pairing/`) -3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api)) +3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-rest-apis)) 4. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. > Note: The initiator node could be the HTTP server or the HTTP client @@ -926,7 +942,7 @@ Before an node can initiate a connection, it needs three things. 1. The HTTP server and the HTTP client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. 2. The HTTP client must have the base URL of the connection API (e.g. `https://hostname.local/connection/`) -3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api)) +3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-rest-apis)) 4. The two nodes must have been paired successfully and must have an accessToken for this pairing If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the connection process. From b5a98a5b53342b76363b6016a7257c084b370256 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 12 May 2026 14:26:40 +0200 Subject: [PATCH 10/67] Specified all LAN-LAN pre-pairing interaction --- .../discovery-pairing-authentication.md | 72 ++++++++++++++++++- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 91360c8..55e8f11 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -529,13 +529,79 @@ Where: | `F` | SHA256 fingerprint of the TLS server certificate of the HTTP server | | `\|\|` | Concatenation | +## Getting endpoint information + +> This section is only applicable for LAN-LAN pairing + +Once a LAN endpoint has discovered a LAN pairing endpoint (through DNS-SD or by the end user manually entering the URL), it still knows very little about the endpoint. There are two REST operations that allow an HTTPS client to query information of a server endpoint: performing a GET on `/endpoint` and performing a GET on `/nodes`. + +These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the initiator node is the HTTPS client and the responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). + +Before the HTTPS client can start interaction with the server, it must first select a version of the API to use. See [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api). For full normative details see the OpenAPI specification files. + +* The client can perform a HTTPS GET request on the path `/endpoint` to receive the endpoint details of the endpoint. +* The client can perform a HTTPS GET request on the path `/nodes` to receive a list of node details for all the nodes represented by the endpoint. + +There is no authentication for these operations, but the server **must** check if the request originates from within the LAN (see [LAN deployment check](#lan-deployment-check)). If it does not, the server **must** respond with HTTP status code 401. The server **must** accept requests with a self-signed certificate. + ## Pre-pairing interaction > This section is only applicable for LAN-LAN pairing -> TODO: This section needs to be improved +Once the end user has selected a responder node it wants the initiator node to pair with, the end user probably still has to retrieve the pairing code from the responder node. In order to improve the user experience, the initiator node can send a *prepare pairing* signal to the responder node. The responder node **may** use this signal to proactively show the pairing code in its user interface, for example in the form of a pop-up or notification. This saves the end user the trouble of searching where to find the pairing token in the user interface. It is also possible for the initiator node to send a *cancel prepare pairing* signal to the responder node, in case the end user has no longer selected the responder node it wants to pair with. Sending these signals **must** be implemented by the client, but only when there is a clear distinction between the moment the prepare pairing signal is sent and when the actual paring starts. The receiver of these signals **may** process these signals by showing the pairing token in its user interface. When the prepare pairing signal is sent, it is not guaranteed that a cancel prepare pairing or a pairing attempt will follow. + +These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the initiator node is the HTTPS client and the responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). + +There is no authentication for these operations, but the server **must** check if the request originates from within the LAN (see [LAN deployment check](#lan-deployment-check)). If it does not, the server **must** respond with HTTP status code 401. The server **must** accept requests with a self-signed certificate. + +Before sending signals the HTTPS client **must** have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api)). + +## Sending the prepare pairing signal + +The client can send the prepare pairing signal to the server by sending an HTTPS POST request to the path `/preparePairing`. The client must perform the following checks before sending information: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Sending signal failed, do not proceed with sending signal | +| Check if the server is in the LAN | Sending signal failed, do not proceed with sending signal | + +The client **must** send the following information in the request. For full normative details see the OpenAPI specification files. + +| Property | Description | +| --- | --- | +| `clientNodeDescription` | Details of the node at the client that the end user intents to pair with the node at the server | +| `clientEndpointDescription` | Details of the client endpoint | +| `serverNodeId` | The node ID of the node at the server that the end user intents to pair weth the node at the client (see [Getting endpoint information](#getting-endpoint-information) for details on how to retrieve the server node ID) | + +The server **must** perform the checks in the table below. For the checks with HTTP status 400, a `PairingResponseErrorMessage` must be send. In that case, the contents of the `additionalInfo` field is supposed the be helpful and up to the implementer. + +| Check | Status code | Type of `PairingResponseErrorMessage` when check fails | +| --- | --- | --- | +| Did the request originate from within the same LAN? | 401 | n/a | +| Is the request properly formatted and does it follow the schema? | 400 | `ParsingError` | +| Does it recognize the `serverNodeId`? | 400 | `NodeNotFound` | +| Are the endpoint and node ready for pairing? | 400 | `Other` | +| Does the targeted node have a different role than the initiator node (i.e. you cannot pair two RM's or two CEM's)? | 400 | `InvalidCombinationOfRoles` | + +If no checks fail the server **should** respond with HTTP status code 204. + +## Cancelling the prepare pairing signal + +If the client sent a prepare pairing signal the the server, and the end user has indicated in some way that it is no longer indented to pair with the node, it **should** send a cancel prepare pairing signal. It can do that by sending an HTTPS POST request to the path `/cancelPreparePairing`. The client must perform the following checks before sending information: + +| Check | How to proceed if check fails | +| --- | --- | +| Check TLS certificate | Sending signal failed, do not proceed with sending signal | +| Check if the server is in the LAN | Sending signal failed, do not proceed with sending signal | + +The client **must** send the following information in the request. For full normative details see the OpenAPI specification files. + +| Property | Description | +| --- | --- | +| `clientNodeId` | The node ID of the node at the client that the end user intents to pair weth the node at the server | +| `serverNodeId` | The node ID of the node at the server that the end user intents to pair weth the node at the client | -The user visits the S2ClientNodeUI and the S2ServerNode has been discovered (so the S2ServerNode base URL is known) by the the S2ClientNode per [discovery](#discovery) as specified above. The S2ClientNode does a preparePairing HTTP request to let the S2ServerNode know that there is an S2ClientNode that wants to pair. It is up to the S2ServerNode implementation to decide what to do with this signal. It can be used to display a pop-up with the pairing token in its UI to improve the user experience. It must be implemented by the client, but only when there is a clear distinction between the moment preparePairing is called and when requestPairing is called. When preparePairing is called, it is not guaranteed that a call to pairingRequest or cancelPreparePairing will follow so it is recommended to put a time-out on showing the pairing token in the S2ServerNodeUI. +The server **must** check if the request originates from within the LAN. If it doesn't, the server responds with HTTP status code 401. In other cases it responds with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). ## Long-polling @@ -682,7 +748,7 @@ The client **must** perform the following checks during this request: | --- | --- | | Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | | If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | -| Check if same fingerprint is used as previous request | Pairing is failed, do not proceed with the pairing attempt | +| Check if same fingerprint is used as previous request (when applicable) | Pairing is failed, do not proceed with the pairing attempt | If no checks fail the client **should** proceed to the next step. From 91741f42f5ea07e2caa7ed391639df575e54f222 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 12 May 2026 15:02:40 +0200 Subject: [PATCH 11/67] Updated the challenge response function to mitigate WAN mitm-attacks --- .../discovery-pairing-authentication.md | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 91360c8..f9708de 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -505,9 +505,9 @@ The algorithm to calculate the response is based on the HMAC (hash-based message The HMAC function itself uses a cryptographic hash function for its calculations. Since cryptographic hash functions might contain vulnerabilities, this protocol uses a simple selection mechanism for the cryptographic hash function. The HTTP client sends with the requestPairing HTTP request a list of supported hash functions. In the response the HTTP server indicates which hash function it has selected from this list. This function **must** be used for all response calculations during het pairing attempt. Currently there is only one hash function available (SHA256), but other options might be added in the future. -It order to avoid man-in-the-middle attacks when using self-signed certificates, the SHA256 fingerprint of the TLS *server certificate* can also be used as input for calculating the response. +It order to avoid man-in-the-middle attacks, information about the connection in also used as input for calculating the response. When both nodes are deployed in the LAN, the SHA256 fingerprint of the server certificate is used. In other scenario's, the domain name of the server is included in the calculation of the response. -> Note that the pairing token is encoded using Base64, so it must also be decoded using Base64 before it can be used in the challenge response function. +Note that the challenge and response binary data, that they and are encoded using Base64 and must also be decoded before they can be used. SHA256 certificate fingerprints are encoded as a hexadecimal string, and must be decoded as hexadecimal string before it can be used as input. The pairing token and domain name are strings, which need to be encoded into binary data using the ASCII table. The exact function to calculate the response depends on the deployment of the nodes. @@ -516,18 +516,19 @@ When both nodes have a LAN deployment: R = HMAC(C, T || F) When at least one node has a WAN deployment: - R = HMAC(C, T) + R = HMAC(C, T || D) ``` Where: -| Symbol | meaning | -| ------ | ------- | -| `R` | Response -| `HMAC` | HMAC function with the selected cryptographic hash function | -| `C` | Challenge | -| `T` | Pairing token | -| `F` | SHA256 fingerprint of the TLS server certificate of the HTTP server | -| `\|\|` | Concatenation | +| Symbol | Type | Meaning | +| ------ | ------- | ---- | +| `R` | Binary data | Response | +| `HMAC` | Function | HMAC function with the selected cryptographic hash function | +| `C` | Binary data | Challenge | +| `T` | Binary data | Pairing token | +| `F` | Binary data | SHA256 fingerprint of the TLS server certificate (i.e. leaf certificate) | +| `D` | Binary data | The domain name of the HTTP server, including subdomains, without protocol or trailing slashes (e.g. `pairing.s2.example.com`) | +| `\|\|` | Function | Concatenation function | ## Pre-pairing interaction From 82d9b271426eab98c2f9b7eaba0499fd45c7e236 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 19 May 2026 14:26:17 +0200 Subject: [PATCH 12/67] Apply suggestions from code review Co-authored-by: Jorrit Nutma --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index f9708de..b4f7930 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -507,7 +507,7 @@ The HMAC function itself uses a cryptographic hash function for its calculations It order to avoid man-in-the-middle attacks, information about the connection in also used as input for calculating the response. When both nodes are deployed in the LAN, the SHA256 fingerprint of the server certificate is used. In other scenario's, the domain name of the server is included in the calculation of the response. -Note that the challenge and response binary data, that they and are encoded using Base64 and must also be decoded before they can be used. SHA256 certificate fingerprints are encoded as a hexadecimal string, and must be decoded as hexadecimal string before it can be used as input. The pairing token and domain name are strings, which need to be encoded into binary data using the ASCII table. +Note that the challenge and response is binary data. Both are encoded using Base64 and must also be decoded before they can be used. SHA256 certificate fingerprints are encoded as a hexadecimal string, and must be decoded as hexadecimal string before it can be used as input. The pairing token and domain name are strings, which need to be encoded into binary data using the ASCII table. The exact function to calculate the response depends on the deployment of the nodes. From ec78d61647fb1c0dd40795041ba9b8d343303304 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 19 May 2026 14:35:46 +0200 Subject: [PATCH 13/67] Added clarification that fingerprints contain colons --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index b4f7930..5a4c407 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -507,7 +507,7 @@ The HMAC function itself uses a cryptographic hash function for its calculations It order to avoid man-in-the-middle attacks, information about the connection in also used as input for calculating the response. When both nodes are deployed in the LAN, the SHA256 fingerprint of the server certificate is used. In other scenario's, the domain name of the server is included in the calculation of the response. -Note that the challenge and response is binary data. Both are encoded using Base64 and must also be decoded before they can be used. SHA256 certificate fingerprints are encoded as a hexadecimal string, and must be decoded as hexadecimal string before it can be used as input. The pairing token and domain name are strings, which need to be encoded into binary data using the ASCII table. +Note that the challenge and response are binary data. Both are encoded using Base64 and must also be decoded before they can be used. SHA256 certificate fingerprints are encoded into a hexadecimal string, and must be decoded as hexadecimal string before it can be used as input (note that fingerprint strings usually contain colons to separate bytes). The pairing token and domain name are strings, which need to be converted into binary data using the ASCII table. The exact function to calculate the response depends on the deployment of the nodes. From e7dc90449ccd5bce7240d2ea3b8673453204ab07 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 19 May 2026 14:41:55 +0200 Subject: [PATCH 14/67] Apply suggestions from code review Co-authored-by: Jorrit Nutma --- website/s2-connect/discovery-pairing-authentication.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 55e8f11..9ec2e56 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -539,8 +539,8 @@ These operations **must** be implemented by LAN deployed endpoints, but **must n Before the HTTPS client can start interaction with the server, it must first select a version of the API to use. See [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api). For full normative details see the OpenAPI specification files. -* The client can perform a HTTPS GET request on the path `/endpoint` to receive the endpoint details of the endpoint. -* The client can perform a HTTPS GET request on the path `/nodes` to receive a list of node details for all the nodes represented by the endpoint. +* The client can perform an HTTPS GET request on the path `/endpoint` to receive the remote endpoint details. +* The client can perform an HTTPS GET request on the path `/nodes` to receive a list of node details for all the nodes represented by the endpoint. There is no authentication for these operations, but the server **must** check if the request originates from within the LAN (see [LAN deployment check](#lan-deployment-check)). If it does not, the server **must** respond with HTTP status code 401. The server **must** accept requests with a self-signed certificate. @@ -548,13 +548,13 @@ There is no authentication for these operations, but the server **must** check i > This section is only applicable for LAN-LAN pairing -Once the end user has selected a responder node it wants the initiator node to pair with, the end user probably still has to retrieve the pairing code from the responder node. In order to improve the user experience, the initiator node can send a *prepare pairing* signal to the responder node. The responder node **may** use this signal to proactively show the pairing code in its user interface, for example in the form of a pop-up or notification. This saves the end user the trouble of searching where to find the pairing token in the user interface. It is also possible for the initiator node to send a *cancel prepare pairing* signal to the responder node, in case the end user has no longer selected the responder node it wants to pair with. Sending these signals **must** be implemented by the client, but only when there is a clear distinction between the moment the prepare pairing signal is sent and when the actual paring starts. The receiver of these signals **may** process these signals by showing the pairing token in its user interface. When the prepare pairing signal is sent, it is not guaranteed that a cancel prepare pairing or a pairing attempt will follow. +Once the end user has selected a responder node it wants the initiator node to pair with, the end user probably still has to retrieve the pairing code from the responder node. In order to improve the user experience, the initiator node can send a *prepare pairing* signal to the responder node. The responder node **may** use this signal to proactively show the pairing code in its user interface, for example in the form of a pop-up or notification. This saves the end user the trouble of searching where to find the pairing token in the user interface. It is also possible for the initiator node to send a *cancel prepare pairing* signal to the responder node, in case the end user has no longer selected the responder node it wants to pair with. Sending these signals **must** be implemented by the client, but only when there is a clear distinction between the moment the prepare pairing signal is sent and when the actual pairing starts. The receiver of these signals **may** process these signals by showing the pairing token in its user interface. When the prepare pairing signal is sent, it is not guaranteed that a cancel prepare pairing or a pairing attempt will follow. These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the initiator node is the HTTPS client and the responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). -There is no authentication for these operations, but the server **must** check if the request originates from within the LAN (see [LAN deployment check](#lan-deployment-check)). If it does not, the server **must** respond with HTTP status code 401. The server **must** accept requests with a self-signed certificate. +There is no authentication for these operations, but the server **must** check if the request originates from within the LAN (see [LAN deployment check](#lan-deployment-check)). If it does not, the server **must** respond with HTTP status code 401. The client **must** accept requests with a self-signed certificate. -Before sending signals the HTTPS client **must** have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api)). +Before sending signals the HTTPS client **must** have selected the version of the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api)). ## Sending the prepare pairing signal From 1317da059dc0abe35fe3fe0e3a90dce9379b6b8c Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 19 May 2026 15:16:58 +0200 Subject: [PATCH 15/67] Removed LAN check from pre-pairing interactions --- website/s2-connect/discovery-pairing-authentication.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 9ec2e56..26a11bc 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -542,7 +542,7 @@ Before the HTTPS client can start interaction with the server, it must first sel * The client can perform an HTTPS GET request on the path `/endpoint` to receive the remote endpoint details. * The client can perform an HTTPS GET request on the path `/nodes` to receive a list of node details for all the nodes represented by the endpoint. -There is no authentication for these operations, but the server **must** check if the request originates from within the LAN (see [LAN deployment check](#lan-deployment-check)). If it does not, the server **must** respond with HTTP status code 401. The server **must** accept requests with a self-signed certificate. +Note that there is no authentication for these operations. It is assumed that these operations are only exposed within the LAN. ## Pre-pairing interaction @@ -552,7 +552,7 @@ Once the end user has selected a responder node it wants the initiator node to p These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the initiator node is the HTTPS client and the responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). -There is no authentication for these operations, but the server **must** check if the request originates from within the LAN (see [LAN deployment check](#lan-deployment-check)). If it does not, the server **must** respond with HTTP status code 401. The client **must** accept requests with a self-signed certificate. +Note that there is no authentication for these operations. It is assumed that these operations are only exposed within the LAN. Before sending signals the HTTPS client **must** have selected the version of the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api)). @@ -563,7 +563,6 @@ The client can send the prepare pairing signal to the server by sending an HTTPS | Check | How to proceed if check fails | | --- | --- | | Check TLS certificate | Sending signal failed, do not proceed with sending signal | -| Check if the server is in the LAN | Sending signal failed, do not proceed with sending signal | The client **must** send the following information in the request. For full normative details see the OpenAPI specification files. @@ -577,7 +576,6 @@ The server **must** perform the checks in the table below. For the checks with H | Check | Status code | Type of `PairingResponseErrorMessage` when check fails | | --- | --- | --- | -| Did the request originate from within the same LAN? | 401 | n/a | | Is the request properly formatted and does it follow the schema? | 400 | `ParsingError` | | Does it recognize the `serverNodeId`? | 400 | `NodeNotFound` | | Are the endpoint and node ready for pairing? | 400 | `Other` | @@ -592,7 +590,6 @@ If the client sent a prepare pairing signal the the server, and the end user has | Check | How to proceed if check fails | | --- | --- | | Check TLS certificate | Sending signal failed, do not proceed with sending signal | -| Check if the server is in the LAN | Sending signal failed, do not proceed with sending signal | The client **must** send the following information in the request. For full normative details see the OpenAPI specification files. @@ -601,7 +598,7 @@ The client **must** send the following information in the request. For full norm | `clientNodeId` | The node ID of the node at the client that the end user intents to pair weth the node at the server | | `serverNodeId` | The node ID of the node at the server that the end user intents to pair weth the node at the client | -The server **must** check if the request originates from within the LAN. If it doesn't, the server responds with HTTP status code 401. In other cases it responds with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). +The server **should** respond with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). ## Long-polling From e26cc83792f816d7a93212675027bd3de64eb42d Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 19 May 2026 15:24:42 +0200 Subject: [PATCH 16/67] Apply suggestions from code review Co-authored-by: Jorrit Nutma --- .../s2-connect/discovery-pairing-authentication.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 65624b0..fbdad5e 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -381,17 +381,17 @@ A CEM can be paired with multiple RM's a the same time. A RM can only be paired ## Discovery -In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing endpoint of the other node. In other words, the discovery process is a way to provide an node with the URL of another node which is needed to start the pairing process. Alternatively, it **must** always possible to initiate the pairing by manually providing the URL by the end user. +In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing endpoint URL of the other node. In other words, the discovery process is a way to provide a node with the URL of another node which is needed to start the pairing process. Alternatively, it **must** always be possible to initiate the pairing by manually providing the URL by the end user. -There are two mechanisms for discovery: For discovering WAN endpoints there is a central online registry. For discovering endpoints within the same LAN, DNS-SD is used. DNS-SD can also be used to discovery WAN endpoints of devices that have a presence in the LAN (typically an energy smart appliance which hosts the RM in the cloud). +There are two mechanisms for discovery: For discovering WAN endpoints there is a central online registry. For discovering endpoints within the same LAN, DNS-SD is used. DNS-SD can also be used to discovery WAN endpoints of devices that have a presence in the LAN (typically an energy smart appliance of which the RM is deployed in the cloud). ### WAN pairing endpoint registry > Note: At this point the registry is specified, but not yet publicly available -The task of the registry is to facilitate a more user friendly way to determine the URL of the WAN pairing endpoint. Owners of an S2 Connect WAN pairing endpoint can register their endpoint at the registry. The user interface of a CEM or RM could show a list of relevant endpoints to the user (e.g. in a list or drop down menu) with details that would be easily recognizable to the end user (e.g. name and icon). By querying the registry the user interface can always show an up-to-date list of endpoints. The registry contains filtering functionality to filter endpoints that are relevant in the context. +The purpose of the registry is to facilitate a more user friendly way to determine the URL of the WAN pairing endpoint. Providers of an S2 Connect WAN pairing endpoint can register their endpoint at the registry. The user interface of a CEM or RM could show a list of relevant endpoints to the user (e.g., in a list or drop down menu) with details that would be easily recognizable to the end user (e.g., name and icon). By querying the registry, the user interface can always show an up-to-date list of endpoints. The registry contains filtering functionality to filter endpoints that are relevant in the context. -The registry uses the same versioning mechanism as the other S2 Connect OpenAPI files. See [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis) to see how the client can select the version of the API to use. +The registry uses the same version negotiation mechanism as the other S2 Connect OpenAPI files. Refer to [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis) for information on how clients can select the API version to use. The registry contains the following information for each endpoint. For full normative details see the OpenAPI specification files. @@ -419,11 +419,11 @@ In order to filter out the relevant endpoint records the API supports the follow In addition, the number of responses can be limited. An offset can also be provided in order to split the results over multiple requests. ### DNS-SD based discovery -DNS-SD is used for automatically discover nodes from an node that is deployed in the LAN. This method can be used in three ways. +DNS-SD is used to automatically discover nodes from a node that is deployed in the LAN. This method can be used in three ways. * To discover another node that is deployed in the LAN, which is the responder node * To advertise a [long polling URL](#long-polling) so other initiator nodes in the LAN could connect to this node -* To discover an node of which the RM is deployed in the WAN, but that also has a presence in the LAN. +* To discover a node of which the RM is deployed in the WAN, but that also has a presence in the LAN. S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it is an HTTP based protocol. S2 Connect uses the following DNS-SD values: From bad648c4ed4bf007e3e99ed0ace329b96e8c991e Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 19 May 2026 15:28:54 +0200 Subject: [PATCH 17/67] Textual improvements regarding the different APIs --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index fbdad5e..c08ffc7 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -297,7 +297,7 @@ This document serves as an overall specification of the S2 Connect protocol. How | S2 JSON message structure | The types of S2 messages that can be exchanges between nodes | JSON schema files | [Github](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema) | ## Versioning of OpenAPI files -The S2 Connect pairing API, the S2 Connect connection initiation API and the S2 Connect WAN pairing endpoint registry are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. All S2 Connect OpenAPI files share the same version number. +The pairing API, the connection initiation API and the WAN pairing endpoint registry API are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. All S2 Connect OpenAPI files share the same version number. The minor version is increased when backwards compatible changes are made. Be aware that we consider adding items to certain lists of enums (e.g. the list of supported hash functions) backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. From c2cdce84ddc336e0b2e39f28dda5e21394978c42 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Tue, 19 May 2026 15:30:44 +0200 Subject: [PATCH 18/67] Created specifications navbar item --- structured-documentation/ControlType.toml | 2 +- .../EnergyManagementRole.toml | 2 +- .../SelectControlType.toml | 2 +- .../{ => learn}/background/_category_.yml | 0 .../background/intro-energy-flexibility.md | 2 +- .../background/why-the-s2-approach.md | 0 .../communication-layer/_category_.yml | 0 .../communication-layer/accepted-crypto.md | 0 .../communication-layer/introduction.md | 4 ++- .../communication-layer/why-not-oauth.md | 2 +- .../docs/{ => learn}/concepts/_category_.yml | 0 .../docs/{ => learn}/concepts/architecture.md | 0 .../{ => learn}/concepts/common-concepts.md | 0 .../{ => learn}/concepts/control-types.md | 0 .../{ => learn}/concepts/operation-modes.md | 0 .../docs/{ => learn}/examples/_category_.yml | 0 website/docs/{ => learn}/examples/ev.md | 6 ++-- .../docs/{ => learn}/examples/heat-pump.md | 6 ++-- website/docs/{ => learn}/examples/pv.md | 4 +-- website/docs/{ => learn}/welcome.md | 10 +++--- website/docs/specs/specs.md | 18 ++++++++++ website/docusaurus.config.ts | 33 ++++++++++++------- ...reading-this-documentation.md => index.md} | 0 .../discovery-pairing-authentication.md | 5 ++- .../discovery-pairing-authentication.md | 4 +-- website/sidebars.ts | 31 +++++++++-------- website/src/pages/index.tsx | 4 +-- 27 files changed, 85 insertions(+), 50 deletions(-) rename website/docs/{ => learn}/background/_category_.yml (100%) rename website/docs/{ => learn}/background/intro-energy-flexibility.md (98%) rename website/docs/{ => learn}/background/why-the-s2-approach.md (100%) rename website/docs/{ => learn}/communication-layer/_category_.yml (100%) rename website/docs/{ => learn}/communication-layer/accepted-crypto.md (100%) rename website/docs/{ => learn}/communication-layer/introduction.md (88%) rename website/docs/{ => learn}/communication-layer/why-not-oauth.md (99%) rename website/docs/{ => learn}/concepts/_category_.yml (100%) rename website/docs/{ => learn}/concepts/architecture.md (100%) rename website/docs/{ => learn}/concepts/common-concepts.md (100%) rename website/docs/{ => learn}/concepts/control-types.md (100%) rename website/docs/{ => learn}/concepts/operation-modes.md (100%) rename website/docs/{ => learn}/examples/_category_.yml (100%) rename website/docs/{ => learn}/examples/ev.md (98%) rename website/docs/{ => learn}/examples/heat-pump.md (98%) rename website/docs/{ => learn}/examples/pv.md (98%) rename website/docs/{ => learn}/welcome.md (91%) create mode 100644 website/docs/specs/specs.md rename website/model-reference/{reading-this-documentation.md => index.md} (100%) diff --git a/structured-documentation/ControlType.toml b/structured-documentation/ControlType.toml index 895a6c0..3b5727b 100644 --- a/structured-documentation/ControlType.toml +++ b/structured-documentation/ControlType.toml @@ -4,7 +4,7 @@ Describes a resource's control type(s). Control types describe how a resource is flexible. Typically, an RM will implement one control type, while a CEM will implement all five control types. """ -see_also = [{ name = "Guide to S2 control types", link = "https://docs.s2standard.org/docs/concepts/control-types/" }] +see_also = [{ name = "Guide to S2 control types", link = "https://docs.s2standard.org/docs/learn/concepts/control-types/" }] [variants.POWER_ENVELOPE_BASED_CONTROL] variant_name = "POWER_ENVELOPE_BASED_CONTROL" diff --git a/structured-documentation/EnergyManagementRole.toml b/structured-documentation/EnergyManagementRole.toml index 8ec73f0..0a78854 100644 --- a/structured-documentation/EnergyManagementRole.toml +++ b/structured-documentation/EnergyManagementRole.toml @@ -2,7 +2,7 @@ type_name = "EnergyManagementRole" description = """ Describes whether a resource is an RM or CEM. """ -see_also = [{ name = "Guide to the S2 architecture", link = "https://docs.s2standard.org/docs/concepts/architecture/"}] +see_also = [{ name = "Guide to the S2 architecture", link = "https://docs.s2standard.org/docs/learn/concepts/architecture/"}] [variants.CEM] variant_name = "CEM" diff --git a/structured-documentation/SelectControlType.toml b/structured-documentation/SelectControlType.toml index 60fecd6..5765a3a 100644 --- a/structured-documentation/SelectControlType.toml +++ b/structured-documentation/SelectControlType.toml @@ -5,7 +5,7 @@ Activates a Control Type for this session. This message is sent by the CEM to activate a Control Type for this session. The activated control type must be supported by the Resource Manager (as indicated in `ResourceManagerDetails` received by the CEM). """ -see_also = [{ name = "Guide to S2 control types", link = "https://docs.s2standard.org/docs/concepts/control-types/" }] +see_also = [{ name = "Guide to S2 control types", link = "https://docs.s2standard.org/docs/learn/concepts/control-types/" }] [fields.control_type] field_name = "control_type" diff --git a/website/docs/background/_category_.yml b/website/docs/learn/background/_category_.yml similarity index 100% rename from website/docs/background/_category_.yml rename to website/docs/learn/background/_category_.yml diff --git a/website/docs/background/intro-energy-flexibility.md b/website/docs/learn/background/intro-energy-flexibility.md similarity index 98% rename from website/docs/background/intro-energy-flexibility.md rename to website/docs/learn/background/intro-energy-flexibility.md index f9dbc02..c8fc5be 100644 --- a/website/docs/background/intro-energy-flexibility.md +++ b/website/docs/learn/background/intro-energy-flexibility.md @@ -32,7 +32,7 @@ On the other hand, there are many types of optimizations an energy management sy With a common protocol for energy flexibility, any device can work with any energy management system. With a common protocol for energy flexibility, you are free to choose which devices you buy *and* the energy management system that optimizes energy flexibility the way you want. -For more information on why S2 was designed the way it was designed, take a look at [Why the S2 approach?](/docs/background/why-the-s2-approach.md). +For more information on why S2 was designed the way it was designed, take a look at [Why the S2 approach?](//learn/background/why-the-s2-approach.md). ## How does S2 relate to other protocols for energy management? How S2 relates exactly to another protocol of course depends on the other protocol. But most protocols out there tend to pick a type of device (e.g. EV charger, white goods) or pick an optimization (e.g. tariffs, grid signals). S2 is unique in that it is agnostic to both aspects. diff --git a/website/docs/background/why-the-s2-approach.md b/website/docs/learn/background/why-the-s2-approach.md similarity index 100% rename from website/docs/background/why-the-s2-approach.md rename to website/docs/learn/background/why-the-s2-approach.md diff --git a/website/docs/communication-layer/_category_.yml b/website/docs/learn/communication-layer/_category_.yml similarity index 100% rename from website/docs/communication-layer/_category_.yml rename to website/docs/learn/communication-layer/_category_.yml diff --git a/website/docs/communication-layer/accepted-crypto.md b/website/docs/learn/communication-layer/accepted-crypto.md similarity index 100% rename from website/docs/communication-layer/accepted-crypto.md rename to website/docs/learn/communication-layer/accepted-crypto.md diff --git a/website/docs/communication-layer/introduction.md b/website/docs/learn/communication-layer/introduction.md similarity index 88% rename from website/docs/communication-layer/introduction.md rename to website/docs/learn/communication-layer/introduction.md index 5888f7e..542f861 100644 --- a/website/docs/communication-layer/introduction.md +++ b/website/docs/learn/communication-layer/introduction.md @@ -14,4 +14,6 @@ S2 was designed as a semantic protocol, which can have multiple, mutually compat * communication between a device endpoint in the cloud and an energy management system in the building * communication between different modules inside an energy management system -S2 Connect specifies the discovery, pairing, communication and unpairing process between two S2 Connect capable devices. \ No newline at end of file +S2 Connect specifies the discovery, pairing, communication and unpairing process between two S2 Connect capable devices. + +Refer to [specification](/s2-connect/discovery-pairing-authentication) to read the S2-Connect specification. \ No newline at end of file diff --git a/website/docs/communication-layer/why-not-oauth.md b/website/docs/learn/communication-layer/why-not-oauth.md similarity index 99% rename from website/docs/communication-layer/why-not-oauth.md rename to website/docs/learn/communication-layer/why-not-oauth.md index 6859cfd..61fece5 100644 --- a/website/docs/communication-layer/why-not-oauth.md +++ b/website/docs/learn/communication-layer/why-not-oauth.md @@ -12,7 +12,7 @@ Given our requirements on the pairing and authentication process, the following The pairing and authentication process based on OAuth 2.0 would be specified as follows: -![image](../../static/img/communication-layer/oauth2-s2.png) +![image](@site/static/img/communication-layer/oauth2-s2.png)
Image generated using the following PlantUML code: diff --git a/website/docs/concepts/_category_.yml b/website/docs/learn/concepts/_category_.yml similarity index 100% rename from website/docs/concepts/_category_.yml rename to website/docs/learn/concepts/_category_.yml diff --git a/website/docs/concepts/architecture.md b/website/docs/learn/concepts/architecture.md similarity index 100% rename from website/docs/concepts/architecture.md rename to website/docs/learn/concepts/architecture.md diff --git a/website/docs/concepts/common-concepts.md b/website/docs/learn/concepts/common-concepts.md similarity index 100% rename from website/docs/concepts/common-concepts.md rename to website/docs/learn/concepts/common-concepts.md diff --git a/website/docs/concepts/control-types.md b/website/docs/learn/concepts/control-types.md similarity index 100% rename from website/docs/concepts/control-types.md rename to website/docs/learn/concepts/control-types.md diff --git a/website/docs/concepts/operation-modes.md b/website/docs/learn/concepts/operation-modes.md similarity index 100% rename from website/docs/concepts/operation-modes.md rename to website/docs/learn/concepts/operation-modes.md diff --git a/website/docs/examples/_category_.yml b/website/docs/learn/examples/_category_.yml similarity index 100% rename from website/docs/examples/_category_.yml rename to website/docs/learn/examples/_category_.yml diff --git a/website/docs/examples/ev.md b/website/docs/learn/examples/ev.md similarity index 98% rename from website/docs/examples/ev.md rename to website/docs/learn/examples/ev.md index 0b1c0fb..803f5d8 100644 --- a/website/docs/examples/ev.md +++ b/website/docs/learn/examples/ev.md @@ -3,7 +3,7 @@ title: "Example: Electric Vehicle" hide_title: true --- -![image not found](../../static/img/example-ev-header.png) +![image not found](@site/static/img/example-ev-header.png) This page serves as a guide for implementing a S2 RM for an electric vehicle (charge point), or to give a CEM developer a better understanding of what to expect when controlling the energy flexibility of electric vehicles. It provides some example S2 messages for a (fictional) EV as well. @@ -19,7 +19,7 @@ The battery management system in the EV consists of hardware and software that u In principle, the RM can be implemented either on EV level or on the charge point level. However, the availability of the required data for the RM depends on the interfaces between EV and charge point (so both the EV and the charge point need to support right interfaces). For example, some EVs do not share State-of-Charge (SoC) information with the charge point and, on the other hand, the EV typically doesn't know where it is connected in the grid. Depending on the energy flexibility use case and as long as the RM has the required data, it can be implemented both on EV or on charge point level. However, in a specific situation, there should only be one RM for the charger-EV combination. ## Choosing the right Control Type -S2 is a language for conveying energy flexibility to the CEM without making any assumptions on what the CEM is optimizing for. Although there are multiple [Control Type(s)](/docs/concepts/control-types.md) that can be used for an EV, it is preferred to pick the one that conveys the most information and control options to the CEM, so the CEM can do the best optimization that adds the most value. In the ideal situation, the RM has access to the following data points: +S2 is a language for conveying energy flexibility to the CEM without making any assumptions on what the CEM is optimizing for. Although there are multiple [Control Type(s)](learn/concepts/control-types.md) that can be used for an EV, it is preferred to pick the one that conveys the most information and control options to the CEM, so the CEM can do the best optimization that adds the most value. In the ideal situation, the RM has access to the following data points: - Minimum charging power (and discharging power, only relevant for V2G) - Maximum charging power (and discharging power, only relevant for V2G) - SoC at arrival @@ -62,7 +62,7 @@ We cover the situation in which the RM has access to the following data: Furthermore, the RM can control the charging power of the EV such that it can set the power at any value between the minimum and maximum value. -The following sequence diagram is an example of how a message exchange between the CEM and RM could look like, but messages could also be sent in a different order (see also [State of communication](/docs/communication-layer/discovery-pairing-authentication/#state-of-communication)). `ReceptionStatus` messages are omitted for readability. +The following sequence diagram is an example of how a message exchange between the CEM and RM could look like, but messages could also be sent in a different order (see also [State of communication](/s2-connect/discovery-pairing-authentication/#state-of-communication)). `ReceptionStatus` messages are omitted for readability. ![cem_rm_interactions](https://www.plantuml.com/plantuml/png/VP7DJiCm3CVlUOey0F007D1W0x539TI2E9lKWcYf7MndQ3mzeI0HMCNLF_y_BA_KGAnCoH4RUjwZ-FLrT-Bxxjm_ujF0OOVc0nDXC1oTgzIVNipy5cZK5zYXw-TPHUrmQRD7pKoARYblIz4YfFXUSrhBAk8Y0JiWU4RPe45CsleFc33Ocic4q_qXB_itt4Emk0VxatJJNwtFXeCQJXlK835RP78kCMSVoHFx368nb0JYMKQKLdx7RoWTWXR12ScElI-35J2MmS2A7aTNL4_yfks5CzwIcfGmTsgapVXB-P17C7H8e_zi_gl6WAfriSVNWchk_x-FKNzUlwf-PKeV)
diff --git a/website/docs/examples/heat-pump.md b/website/docs/learn/examples/heat-pump.md similarity index 98% rename from website/docs/examples/heat-pump.md rename to website/docs/learn/examples/heat-pump.md index e4f0847..8176f58 100644 --- a/website/docs/examples/heat-pump.md +++ b/website/docs/learn/examples/heat-pump.md @@ -3,7 +3,7 @@ title: "Example: Heat Pump" hide_title: true --- -![image not found](../../static/img/example-heat-pumps-header.png) +![image not found](@site/static/img/example-heat-pumps-header.png) This page serves as a guide for implementing an S2 RM for a heat pump, or to give a CEM developer a better understanding of what to expect when controlling the energy flexibility of a heat pump. It provides some example S2 messages for same (fictional) heat pumps. @@ -27,7 +27,7 @@ A hybrid heat pump can temporarily reduce or stop its electricity consumption by ## Choosing the right Control Type -When implementing S2, it's important to choose the right [Control Type(s)](/docs/concepts/control-types.md) to implement in the RM of the heat pump. S2 is a language for conveying energy flexibility to the CEM without making any assumptions on what the CEM is optimizing for. Although there typically are multiple Control Types that can be used for a heat pump, it is preferred to pick the one that conveys the most information and control options to the CEM, so the CEM can do the best optimization that adds the most value. +When implementing S2, it's important to choose the right [Control Type(s)](learn/concepts/control-types.md) to implement in the RM of the heat pump. S2 is a language for conveying energy flexibility to the CEM without making any assumptions on what the CEM is optimizing for. Although there typically are multiple Control Types that can be used for a heat pump, it is preferred to pick the one that conveys the most information and control options to the CEM, so the CEM can do the best optimization that adds the most value. As a general rule, when there is some form of energy (in this case heat) buffering involved, Fill Rate Base Control (FRBC) is the best Control Type. When there is no flexibility involved, but there is an option to switch between different fuels (as is the case with the hybrid heat pump), Demand Driven Based Control (DDBC) typically is the best option. Both Control Types give the CEM relatively direct control over the heat pump. @@ -54,7 +54,7 @@ The possible Control Types to implement are summarized in the table below. In this example we will work out the communication between CEM and RM for an all electric heat pump that utilizes the DHW buffer for energy flexibility using FRBC. -The following sequence diagram is an example of what a message exchange between a CEM and RM could look like, but messages could also be sent in a different order (see also [State of communication](/docs/communication-layer/discovery-pairing-authentication/#state-of-communication)). `ReceptionStatus` messages are omitted for readability. +The following sequence diagram is an example of what a message exchange between a CEM and RM could look like, but messages could also be sent in a different order (see also [State of communication](/s2-connect/discovery-pairing-authentication/#state-of-communication)). `ReceptionStatus` messages are omitted for readability. ![XPBVhX8n4CRl-nHz0FK1l308I2Hn4xCByNhiVe66RcUTcMBmwLqW9-bXEJcty_l-TD9Pg95O6P9pcGsUpn2_-jPyy6tpsV_2xux32UO3vunWw9sRgF_uvSQ_K-xrI2UuzZQRK3ryfcoX8sV5qxvjZXOfnN_NdYfoCd6HW8Oo7I1h6CMaTNw7X60hpYQSTfVnUNx5FGMBPxA7fasxN](https://github.com/flexiblepower/s2-ws-json/assets/851310/49422881-d6e3-4ffc-b513-74230d61875d)
diff --git a/website/docs/examples/pv.md b/website/docs/learn/examples/pv.md similarity index 98% rename from website/docs/examples/pv.md rename to website/docs/learn/examples/pv.md index e7eba0b..fcc9689 100644 --- a/website/docs/examples/pv.md +++ b/website/docs/learn/examples/pv.md @@ -3,7 +3,7 @@ title: "Example: PV Installation" hide_title: true --- -![image not found](../../static/img/example-pv-header.png) +![image not found](@site/static/img/example-pv-header.png) This page serves as a guide for implementing an S2 RM for a PV installation. It provides example S2 messages that help developers to understand how to interact with PV installations and what kind of flexibility they can offer. @@ -32,7 +32,7 @@ Power Envelope Based Control is used for devices that can be influenced to use o ## Example of controlling an PV inverter using Power Envelope Based control (PEBC) This example describes how a PV inverter can expose its curtailment capabilities to the CEM and let the CEM control this inverter by sending it messages that will ask it to curtail itself. -The following sequence diagram is an example of what a message exchange between a CEM and RM could look like, but messages could also be sent in a different order (see also [State of communication](/docs/communication-layer/discovery-pairing-authentication/#state-of-communication)). ReceptionStatus messages are omitted for readability. +The following sequence diagram is an example of what a message exchange between a CEM and RM could look like, but messages could also be sent in a different order (see also [State of communication](/s2-connect/discovery-pairing-authentication/#state-of-communication)). ReceptionStatus messages are omitted for readability. First an overview of the messages is presented graphically, next sections will describe the example messages in more detail. diff --git a/website/docs/welcome.md b/website/docs/learn/welcome.md similarity index 91% rename from website/docs/welcome.md rename to website/docs/learn/welcome.md index 3a57789..935590b 100644 --- a/website/docs/welcome.md +++ b/website/docs/learn/welcome.md @@ -23,12 +23,12 @@ As the S2 standard is a semantic protocol, it needs an underlying implementing p An important implementing protocol is **S2 Connect**. It is an open source specification using common web technologies that allow for IP-based communication of the S2 standard. A key feature of the specification is that it allows for device connections and energy managers to run on-premises (LAN) or in the cloud (WAN), in all possible combinations. It specifies the discovery, pairing, communication and unpairing process between two devices that want to communicate via S2. S2 Connect uses the JSON schemas of S2 JSON. ## What's next? -For reading a bit more about energy flexibility in general and how S2 relates to it, you can find more information [here](/docs/background/intro-energy-flexibility.md). +For reading a bit more about energy flexibility in general and how S2 relates to it, you can find more information [here](learn/background/intro-energy-flexibility.md). -If you want to know more about the design principles of S2, you can continue reading here at [link](/docs/background/why-the-s2-approach.md). +If you want to know more about the design principles of S2, you can continue reading here at [link](learn/background/why-the-s2-approach.md). -Before starting to implement S2, make sure to familiarize yourself with the [concepts](/docs/concepts/architecture.md). +Before starting to implement S2, make sure to familiarize yourself with the [concepts](learn/concepts/architecture.md). -More information about the implementing protocol specification is provided [here](docs/communication-layer/introduction.md). +More information about the implementing protocol specification is provided [here](learn/communication-layer/introduction.md). -If you want to read about how to model energy flexibility from some typical devices, head over to the [examples](/docs/examples/ev.md). +If you want to read about how to model energy flexibility from some typical devices, head over to the [examples](learn/examples/ev.md). diff --git a/website/docs/specs/specs.md b/website/docs/specs/specs.md new file mode 100644 index 0000000..e4daa0c --- /dev/null +++ b/website/docs/specs/specs.md @@ -0,0 +1,18 @@ +--- +title: Overview +--- + +# Overview of specifications + +## The S2 Standard +The official standard (EN 50491-12-2) that has been approved by European Standardization bodies through CEN/CENELEC. To acquire access to the standard please refer to a national standardization body that is member of CEN/CENELEC. + +## S2-Json +A json-schema definition that describes all S2 messages. + +The json-schema can be found here: [s2-json](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema). + +The data model is also represented on this documentation website, see [Reference](/model-reference). + +## S2-Connect +The S2-Connect specification allows for full stack interoperable S2 solutions. It specifies how S2 appliances can discovery each other, how they pair and which communication protocol to use. The specification is published as open-access [here](/s2-connect/discovery-pairing-authentication). \ No newline at end of file diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 3dd4c98..b7b2961 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -26,6 +26,7 @@ const config: Config = { markdown: { hooks: { onBrokenMarkdownLinks: 'warn', + onBrokenMarkdownImages: 'warn', }, }, @@ -44,6 +45,8 @@ const config: Config = { docs: { sidebarPath: './sidebars.ts', breadcrumbs: false, + // path: 'docs', + // routeBasePath: '/' }, theme: { customCss: './src/css/custom.css', @@ -64,7 +67,8 @@ const config: Config = { path: "s2-connect", routeBasePath: "s2-connect", breadcrumbs: false, - sidebarPath: false + sidebarPath: false, + includeCurrentVersion: true }], ['@docusaurus/plugin-client-redirects', { redirects: [ @@ -73,6 +77,15 @@ const config: Config = { from: ['/docs/communication-layer/discovery-pairing-authentication/'], }, ], + createRedirects(existingPath: string) { + if (existingPath.startsWith('/docs/learn')) { + // Redirect from /docs/team/X to /community/X and /docs/support/X to /community/X + return [ + existingPath.replace('/docs/learn', '/docs') + ]; + } + return undefined; // Return a falsy value: no redirect created + } }] ], @@ -94,24 +107,22 @@ const config: Config = { label: 'Learn', }, { - to: 'model-reference/reading-this-documentation', + to: 'model-reference', activeBaseRegex: 'model-reference', position: 'left', label: 'Reference', }, - // { - // to: 's2-connect/discovery-pairing-authentication', - // activeBaseRegex: 's2-connect', - // position: 'left', - // label: 'S2 Connect', - // }, + { + type: 'docSidebar', + sidebarId: 'specsSidebar', + position: 'left', + label: 'Specifications', + }, { type: 'docsVersionDropdown', position: 'right', - // props passed to the themed navbar item component - our themed override will read `showOn` - versions: ['current', '1.0-beta2'], + // versions: ['current', '1.0-beta2'], docsPluginId: 's2c', - // only show the dropdown when path starts with this prefix }, { href: 'https://github.com/flexiblepower/s2-documentation', diff --git a/website/model-reference/reading-this-documentation.md b/website/model-reference/index.md similarity index 100% rename from website/model-reference/reading-this-documentation.md rename to website/model-reference/index.md diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 91360c8..c6b022b 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1,6 +1,5 @@ --- title: S2 Connect Specification -sidebar_position: 2 --- # S2 Connect - Specification @@ -80,7 +79,7 @@ Serialization: json. The short answer is: oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. We have identified a way to make it work but since it is such non-typical way, we choose not to use oAuth 2.0. -For the long answer, please refer to [this page](why-not-oauth.md). +For the long answer, please refer to [this page](/docs/learn/communication-layer/why-not-oauth). ## Security requirements @@ -1203,4 +1202,4 @@ If the server is in local-local mode, and uses a self-signed CA certificate, the ## Cipher suites -Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. +Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](/docs/learn/communication-layer/accepted-crypto). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. diff --git a/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md b/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md index 291086e..c04743f 100644 --- a/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md +++ b/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md @@ -80,7 +80,7 @@ Serialization: json. The short answer is: oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. We have identified a way to make it work but since it is such non-typical way, we choose not to use oAuth 2.0. -For the long answer, please refer to [this page](why-not-oauth.md). +For the long answer, please refer to [this page](/docs/learn/communication-layer/why-not-oauth). ## Security requirements @@ -1207,4 +1207,4 @@ If the server is in local-local mode, and uses a self-signed CA certificate, the ## Cipher suites -Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. +Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](/docs/learn/communication-layer/accepted-crypto). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. diff --git a/website/sidebars.ts b/website/sidebars.ts index 3f3d91e..808b06e 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -14,21 +14,26 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; */ const sidebars: SidebarsConfig = { // By default, Docusaurus generates a sidebar from the docs folder structure - learnSidebar: [{type: 'autogenerated', dirName: '.'}], - // referenceSidebar: [{type: 'autogenerated', dirName: 'model-reference'}], + learnSidebar: [{type: 'autogenerated', dirName: 'learn'}], + specsSidebar: [{type: 'autogenerated', dirName: 'specs'}] + + // referenceSidebar: [ { + // type: 'autogenerated', + // dirName: 'model-reference', + // }], // But you can create a sidebar manually - /* - tutorialSidebar: [ - 'intro', - 'hello', - { - type: 'category', - label: 'Tutorial', - items: ['tutorial-basics/create-a-document'], - }, - ], - */ + + // referenceSidebar: [ + // 'reading-this', + // 'hello', + // { + // type: 'category', + // label: 'Tutorial', + // items: ['tutorial-basics/create-a-document'], + // }, + // ], + }; export default sidebars; diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx index 8d4bd7c..df82d78 100644 --- a/website/src/pages/index.tsx +++ b/website/src/pages/index.tsx @@ -21,8 +21,8 @@ function HomepageHeader() {

{siteConfig.tagline}

- Read the documentation - Data model reference + Read the documentation + Data model reference
From 9e34fd6709d9ac3ad041927b8e0681bb40403615 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 19 May 2026 15:49:16 +0200 Subject: [PATCH 19/67] Removed WAN endpoint advertisement feature from DNS-SD and improved explenation TXT record --- .../discovery-pairing-authentication.md | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index d2a3d63..785529b 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -236,7 +236,7 @@ On of the main technologies the process relies on is HTTP REST. All interactions The first step is finding the responder node from the initiator node. In principle this is done based on the URL of the responder node. However, to improve user experience, two systems exist to find this URL in a more user friendly manner. For more details see [Discovery](#discovery). * If the responder node is deployed in the WAN, the end user can find the endpoint through the pairing endpoint registry. This would result in a list of vendors that offer nodes. -* If both nodes are deployed in the LAN however, the responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. This process can also be used when an node is deployed in the WAN, but the device also has a presence in the LAN. +* If both nodes are deployed in the LAN however, the responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. ### Pairing @@ -383,7 +383,7 @@ A CEM can be paired with multiple RM's a the same time. A RM can only be paired In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing endpoint URL of the other node. In other words, the discovery process is a way to provide a node with the URL of another node which is needed to start the pairing process. Alternatively, it **must** always be possible to initiate the pairing by manually providing the URL by the end user. -There are two mechanisms for discovery: For discovering WAN endpoints there is a central online registry. For discovering endpoints within the same LAN, DNS-SD is used. DNS-SD can also be used to discovery WAN endpoints of devices that have a presence in the LAN (typically an energy smart appliance of which the RM is deployed in the cloud). +There are two mechanisms for discovery: For discovering WAN endpoints there is a central online registry. For discovering endpoints within the same LAN, DNS-SD is used. ### WAN pairing endpoint registry @@ -419,11 +419,10 @@ In order to filter out the relevant endpoint records the API supports the follow In addition, the number of responses can be limited. An offset can also be provided in order to split the results over multiple requests. ### DNS-SD based discovery -DNS-SD is used to automatically discover nodes from a node that is deployed in the LAN. This method can be used in three ways. +DNS-SD is used to automatically discover nodes from a node that is deployed in the LAN. This method can be used in two ways. * To discover another node that is deployed in the LAN, which is the responder node * To advertise a [long polling URL](#long-polling) so other initiator nodes in the LAN could connect to this node -* To discover a node of which the RM is deployed in the WAN, but that also has a presence in the LAN. S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it is an HTTP based protocol. S2 Connect uses the following DNS-SD values: @@ -445,30 +444,29 @@ Two DNS-SD subtypes are used for endpoints. Subtypes can be used to filter servi * `_rm` is used when the endpoint contains one or more RM node * `_cem` and `_rm` are both used when the endpoint contains both CEM and RM nodes -S2 uses the following TXT records when registering for services. In the table below, M indicates a mandatory value and O indicates on optional value. +S2 uses the following key-value pairs in the TXT record when registering for services. In the table below, M indicates a mandatory value and O indicates on optional value. Note that each value has a maximum length of 255 bytes. | Record name | M/O | Description | --- | --- | --- | -| `txtver` | M | Version of this specification of usage of the TXT record. **Must** be the literal value `1` for this version | +| `txtver` | M | Version of this specification of usage of the TXT record. **Must** be the literal string value `1` for this version | | `e_name` | O | The name of this endpoint (identical to the `name` property in the `EndpointDescription` object as defined in de OpenAPI specification) | | `e_logoUrl` | O | The logoUrl of this endpoint (identical to the `logoUrl` property in the `EndpointDescription` object as defined in de OpenAPI specification) | -| `deployment` | M | **Must** be the literal value `LAN` or `WAN` (identical to the `logoUrl` property in the `EndpointDescription` object as defined in de OpenAPI specification) | | `pairingUrl` | O | The base URL of the pairing API of this endpoint, excluding the version name but including the last slash (e.g. `https://hostname.local/pairing/`). If no value is provided, a `longpollingUrl` **must** be provided. | `longpollingUrl` | O | The base URL of the pairing API of this endpoint on which the longpolling feature is implemented. The URL should be provided excluding the version name but including the last slash (e.g. `https://hostname.local/pairing/`). Only needs to be provided when longpolling is supported. Can only be provided if the value for `deployment` is equal to `LAN`. > Note: It is mandatory to provide a value for at least one of the properties `pairingUrl` and `longpollingUrl`. Providing both is also possible. -The receiver of the service description **must** use the URL provided in the TXT records; not the hostname or IP-address and port associated with the service registry. +The receiver of the service description **must** use the URL provided in the TXT record; not the hostname or IP-address and port associated with the service registry. -> Note: You may have noticed that the full URL of the endpoint is used in the TXT records, even though the endpoint and port are already exposed by DNS-SD itself. This is done to avoid any problems with TLS certificates, which are pinned to a certain domain name. When using a WAN endpoint, the full URL needs to be specified as well, since no local service is actually being offered. +> Note: You may have noticed that the full URL of the endpoint is used in the TXT record, even though the endpoint and port are already exposed by DNS-SD itself. This is done to avoid any problems with TLS certificates, which are bound to a certain mDNS domain name. > Scanning for endpoints could for example be done using the following [avahi](https://avahi.org/) command: > > `avahi-browse -r _s2connect._tcp` > -> Registering an endpoint could for example be done using the folling avahi command: +> Registering an endpoint could for example be done using the following avahi command: > -> `avahi-publish-service -s "EVSE1038" _s2connect._tcp 443 "txtvers=1" "e_name=brand" "deployment=LAN" "pairingUrl=https://EVSE1038.local:443/pairing/" --sub _rm._sub._s2connect._tcp` +> `avahi-publish-service -s "EVSE1038" _s2connect._tcp 443 "txtvers=1" "e_name=brand" "pairingUrl=https://EVSE1038.local:443/pairing/" --sub _rm._sub._s2connect._tcp` ## The pairing token, the node ID alias and the pairing code From a2a0ad066e63e0c11d12775324b94671f1548f36 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 21 May 2026 14:17:41 +0200 Subject: [PATCH 20/67] Rewrote background section, simplified requerements section --- .../discovery-pairing-authentication.md | 91 ++++--------------- 1 file changed, 19 insertions(+), 72 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index d2a3d63..01ec5d1 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -43,92 +43,39 @@ This version of this specification is based on the following versions of the und # Background (informative) -## Requirements - -> TODO: This section needs to be rewritten to better explain the design goals - -The communication layer meets the following requirements: - -The Customer Energy Manager (CEM) and Resource Manager (RM) are logical concepts within the S2 architecture, therefore the S2 standard does not make any assumptions on how and where the CEM and RM are deployed in a real life situation. In practice, the CEM could be deployed on a local gateway in a LAN or as a server somewhere on the internet (WAN), while the RM could be part of the device itself, deployed on an add-on module or on the internet as well. This means that the S2 communication layer **MUST** be able to deal with multiple scenarios that are depicted in the figure below. - -In addition to - and partly because of - supporting the various deployment options, the S2 communication layer has the following generic requirements: - -- Support for full duplex communication. Both sides **MUST** be able to send and receive data simultaneously. -- Communication **MUST** be IP based. -- Communication **MUST** be encrypted. -- Communication latency between CEM and RM or vice versa **MUST** be ≤ 1 second. -- Communication **MUST** work without additional firewall configuration by the end user. -- Implementation of the communication layer **MUST** be based on a widely accepted technology and must be relatively easy to implement. -- The pairing process **SHOULD** support extensibility for other application layer communication protocols. -- Provide a relatively consistent user experience regardless of the deployment of the node -- Run a local RM on a device with constrained hardware -- A RM could not have a UI - -## Technical decisions -Given the requirements, this specification is build on the following high-level technical choices: - -Application layer communication protocol: WebSocket Secure with bearer token authentication. +## Context +S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other over IP networks. S2 Connect builds upon two other projects: -Pairing: Custom HTTP API specified in OpenAPI. +* The **S2 Standard** (formally known as EN50491-12-2) describes the data models and interactions for S2 based communication +* The **S2 JSON** project formalizes the data models of the S2 standard into JSON Schemas for a messages based interaction between CEM and RM -Discovery: DNS-SD (within a LAN) in combination with a central registry (for WAN deployments). +Both the S2 Standard ond S2 JSON can be used independently from S2 Connect. -Serialization: json. - - -**Why not oAuth 2.0?** - -The short answer is: oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. We have identified a way to make it work but since it is such non-typical way, we choose not to use oAuth 2.0. +## Requirements +S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other. The CEM and RM are logical concepts within the S2 architecture, therefore the S2 standard does not make any assumptions on how and where the CEM and RM are deployed in a real life situation. In practice, the CEM could be deployed on a local gateway in a LAN or as a server somewhere on the internet (WAN), while the RM could be part of the device itself, deployed on an add-on module or on the internet as well. S2 Connect provides a single solution which can be used between devices connected through a LAN, on the internet, or a combination of those. -For the long answer, please refer to [this page](why-not-oauth.md). +S2 Connect implements the following high-level requirements: -## Security requirements +- Communication must be secure and remain secure, and must be resilient against attackers from the internet or from within the LAN +- Communication between LAN and WAN must work without additional firewall or NAT configuration by the end user +- S2 Connect must be based on widely accepted technology +- Pairing two devices should be simple enough to execute by most end users +- There must be a relatively consistent end user experience regardless of the deployment of the node +- A local RM can run on a computer with constrained hardware +- It must be possible to implement a RM without relying on a user interface for pairing -The S2 Connect protocol ensures the following four requirements: +The S2 Connect protocol ensures the following four security requirements: 1. Mutual authentication 2. Integrity of communication 3. Confidentiality of communication 4. Forward secrecy -There is one guarantee that explicitly is not given by this protocol: - -5. Non-repudiation - - -### 1. Mutual authentication (guaranteed) - -The mutual authentication is based on the trust relation between the user and the Client/Server. Since it is assumed that the user already had a trust relation with both of them, this existing trust can be used for mutual authentication between the client and the server. Note that the this communication is not part of the S2 Connect specification. - -The end user requests a URL, and token from the server, and gives these to the client. Based on these data, the client can connect to the server, using a TLS connection, check the certificate and authenticate himself with the token. Note that if the server uses a self-signed certificate, the fingerprint will be shared during the pairing phase, so it can be verified by the client. - -### 2. Integrity of communication (guaranteed) - -Using TLS will ensure the integrity of the data. - -### 3. Confidentiality of communication (guaranteed) - -Using TLS will ensure the confidentiality of the data. - -### 4. Forward secrecy (guaranteed) - -Using TLS1.3 will ensure the forward secrecy of the data. - -### 5. Non-repudiation (NOT guaranteed) - -Non-repudiation is not guaranteed in this protocol. Individual messages are not signed by anyone and as a result both parties could deny sending a specific request. However, while no legal proof is given, since integrity and authenticity is guaranteed by TLS, each party always knows for sure which party made what statement. - -### Remaining risk - -There are two remaining vulnerable situations. In this section both will be explained. - -#### self-signed certificates - -In the case that a local RM and a local CEM communicate, it is not in every situation possible to generate a PKI-certificate that can be publicly validated. As a result, S2 accepts, **ONLY** in this situation, self-signed certificates. The risk for spoofing attacks are mitigated by including the certificate fingerprint in the challenge-response process as part of the pairing process, and pinning the self-signed CA certificate at the client side. As a result, the client can check for all connections whether or not it is connected with the correct server. +Non-repudiation is explicitly not guaranteed by this protocol. -#### Trust relations between the end-user and the Client/Server +The entire trust model of S2 Connect is based on the fact that there is already a trust relation between the end user and the CEM and RM. If the CEM and RM do not use adequate security mechanisms, it might be possible to attack the system. -The entire trust model of S2 Connect is based on the fact that there is already a trust relation between the end-user and the client/server. If these clients/servers do not use adequate security mechanisms, it might be possible to attack the S2 system as well. +>> "Why doesn't S2 Connect use oAuth?" is a common question. oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. We have identified a way to make it work but since it is such non-typical way, we choose not to use oAuth 2.0. For more details see [here](why-not-oauth.md). # Terms and definitions (normative) From 0ed80021c9dbdca0f1b6be93281ab3bcd9c5b935 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 21 May 2026 14:36:59 +0200 Subject: [PATCH 21/67] Added recommendation to exponential back-off, moved it one level up so its not specific for websockets --- .../discovery-pairing-authentication.md | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 01ec5d1..853f203 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1062,6 +1062,21 @@ Once the communication server has generated a new pending `accessToken`, it must If the communication client doesn't receive a response to confirming the new `accessToken` (step 7), it does not know if the server has activated the new `accessToken`, or if the old `accessToken` is still in place. It now has (at least) two `accessToken`s in its list, and does not know for certain which one is activate at the communication server. It should try all the accessTokens sequentially. If it finds an `accessToken` that is accepted by the communication server, it can remove the other `accessTokens`. +## Reconnection strategy +After connection initiation, the actual S2 communication starts via the selected transport protocol. At some point this connection will terminate. Unless the reason for termination is that the nodes have been unpaired, the communication client **must** try to reconnect with the communication server. In this case the client **must** always start with the connection initiation process (it is not allowed to reconnect using the transport protocol that was selected last time). + +An exponential back-off strategy for reconnecting **must** be used, increasing the time between reconnection attempts at every failed attempt. It is recommended to use the following strategy for calculating the delay for the nth attempt to reconnect. The delay time starts when a failed connecting attempt is finished. + +`delay_n = random(0, min(max_delay, base_delay × 2^n))` + +Where: + +| Variable | Description or recommended value | +| --- | --- | +| `n` | The number of the reconnection attempt (starting at 0) | +| `base_delay` | 2 seconds | +| `max_delay` | 600 seconds | + ## WebSocket based communication This section specifies how to use WebSocket Secure as the S2-over-TCP/IP application layer protocol. @@ -1112,12 +1127,6 @@ An S2 session can be terminated in different ways: * an node **CAN** terminate the S2 session by sending the S2 terminate message, including an optional earliest time that the session can be restored. The other node can take this into account in planning and (in the case of a client) deciding when to attempt to reconnect. * After two nodes have unpaired, the S2 WebSocket connection **MUST** be terminated immediately. -### Reconnection strategy - -Once an S2 session is terminated it cannot be resumed and if further communication is required, a new session needs to be started. an S2 client node may try to establish a WebSocket connection. - -An exponential back-off strategy **SHOULD** be used, increasing the time between reconnection attempts at every failed attempt. If a reconnection time was included in any termination, S2 client nodes are permitted to make an attempt to reconnect before this time. At the specified time the delay between reconnection attempts **SHOULD** be reset. - ## Communication - JSON messages The S2 standard has been encoded into a JSON schema specification, for details see: [S2 JSON](https://github.com/flexiblepower/s2-ws-json) From 51459c18980a29e2884bbf4466bc3bab7f01d56f Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 21 May 2026 14:52:52 +0200 Subject: [PATCH 22/67] Specified that a WAN pairing server is only inteded for WAN clients (and not LAN clients) --- website/s2-connect/discovery-pairing-authentication.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 853f203..42daff3 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -226,7 +226,7 @@ There are however two situations where this is not possible: * **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that it is not possible to approach a LAN HTTP server from a WAN client. This specifications offers two approaches to this problem: * Accept this limitation and not allow the WAN node to be the initiator node. Pairing can only be performed when the LAN node is the initiator node and the WAN node is the responder node. Special care must be taken to explain this to the end user. - * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement the pairing HTTP server in the cloud, even though the node itself is in the WAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. + * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTP server in the cloud, even though the node itself is in the WAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients. There must always be a method for purely LAN based pairing. * **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTP server is far more memory intensive than an HTTP client, there is an option to implement a LAN RM instance purely as an HTTP client. A long-polling mechanism is available to indicate to the HTTP Server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTP server. In other words: in this specific situation the initiator node behaves as the HTTP server, and the responder node only has to be an HTTP client. ![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) @@ -994,6 +994,7 @@ The server **must** perform the checks in the table below to make sure that it c | Is there overlap between the communication protocols? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleCommunicationProtocols` | Retry later | | Is there overlap between the S2 message versions? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleS2MessageVersions` | Retry later | | Are the endpoint and node ready for pairing? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | +| Is this is a WAN pairing server for a LAN endpoint, does the client have a WAN deployment? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | ### 2. Generate new pending `accessToken` From 03cf6b92f25438d3821807613ec685cc8acb507a Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 21 May 2026 15:17:52 +0200 Subject: [PATCH 23/67] Added DDoS requerements, updated certificate sections --- .../discovery-pairing-authentication.md | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 42daff3..8dbe79b 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1185,17 +1185,16 @@ Client and server **can** keep other (non-security) information for, for example # Security (normative) -> TODO: This section needs to be expanded to explain measures against ddos - ## Brute-force protection -To prevent brute-force pairing request, the server **MUST** implement rate limiting on the requestPairing endpoint. It is up to the server implementation to define the type of rate limiting. +To prevent brute-force pairing request, the server **must** implement rate limiting on the requestPairing endpoint. For more details see [2. Calculate clientHmacChallengeResponse](#2-calculate-clienthmacchallengeresponse). +## DDoS countermeasures +Especilly WAN endpoints are vulnerable for DDoS attacks. It is recommended for WAN endpoints to take countermeasures against these attacks. The nature of these countermeasures are outside of the scope of this spefication. ## TLS Certificates +All HTTP and WebSocket communication uses TCP over TLS with server certificates. The server certificates **must** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. -All HTTP and WebSocket communication uses TCP over TLS with server certificates. The server certificates **MUST** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. - -There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local domain name. This is also the only situation where self-signed certificates are allowed. +There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local mDNS domain name. This is also the only situation where self-signed certificates are allowed. The following image shows the difference. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. @@ -1225,18 +1224,18 @@ SelfSignedCA --> LocalServerCertificate ### Trusting a self-signed root certificate +LAN deployed nodes will have a self-signed root certificate, and a server (leaf) certificate which is signed by the self-signed root certificate. -The self-signed root certificate is by default not trusted. However during the pairing phase, the server with the self-signed root certificate will share the fingerprint of the certificate during the pairing phase as part of the challenge. This will enable the client to verify the self-signed root certificate, and create trust. In this case, the [pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code) **MUST** include the first 9 bytes, encodes as 12 base64 encoded characters, of the fingerprint of this self-signed CA certificate and the client **MUST** check this fingerprint. From this moment on, the client will store the complete fingerprint of the self-signed root certificate, and use it to verify the server certificate for all future connections. +The `endpoint`, `nodes`, `preparePairing` and `cancelPreparePairing` operations can be called before the pairing has happened. For these operations the client **must** accept the self-signed certificates. -Note that the `preparePairing` and `cancelPreparePairing` endpoints can be called before the pairing has happened. So in the case the server is running on a LAN (and thus uses self-signed certificates), the client can skip the certificate validation steps on those endpoint. This means that the HTTP client **must** be configured to accept self-signed certificates during the pairing process. Since the pairing process consists of several HTTP requests, the HTTP client **must** check that for every request the same self-signed certificate is used by the HTTP server. If this is not the case, the HTTP client **cannot** proceed with the request. +Also when attempting pairing (the `requestPairing` operation) the client **must** accept the self signed certificate. During the pairing attempt process trust is esteblished through a two-sided challenge response mechanism. If the two-sided challenge response succeeds, that means that the client can now trust this node. The client **must** store the self-signed root certificate used by the server. Alternatively, when the pairing server becomes the communication client, the pairing client will send the fingerprint of the self-signed root certificate that the communication server will use (see [6B. POST /[version]/postConnectionDetails](#6b-post-versionpostconnectiondetails)). +When performig connection initiation and unpairing the communication client **must** validate that server cerificate and validate check that the certificate was signed by the self-signed root certificate that was stored in the previous step. ### Updating the certificates +A server can update its leaf certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. -A server can update its certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. - -If the server is in local-local mode, and uses a self-signed CA certificate, the CA certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server. If the used crypto for the the CA certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its server certificate at least once every 6 months. - +If the server is deployed in the LAN, and thus uses a self-signed root certificate, the root certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server (or the device that hosts the server). If the used crypto for the the CA certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its leaf certificate at least once every 6 months. ## Cipher suites From 5476328a19ec9c86edf6b96ae8451d0d454a69b9 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 21 May 2026 15:21:39 +0200 Subject: [PATCH 24/67] Added versioning information for S2 JSON --- website/s2-connect/discovery-pairing-authentication.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 8dbe79b..9d757f4 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -252,7 +252,8 @@ The major version is increased when non-backwards compatible changes are made. The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTP server and HTTP clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must server all version on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as port of the URL (e.g. `["v1", "v2"]`). -> TODO: Versioning of S2 JSON Schema's needs to be explained +## Versioning of JSON Schema files +JSON Schema uses its own versioning scheme, which is based on an `major.minor.patch` scheme. When negotiating the S2 JSON version number, the exact version string **must** be used (e.g. `v1.0.0`). ## Addressing endpoints The URL of the pairing and connection API are used in the discovery process, pairing process and connection process, as wel as the basis for TLS certificates. From 37de0474efb2e1041021d059841a8b55c2fdaee9 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 21 May 2026 15:27:47 +0200 Subject: [PATCH 25/67] Specified websocket ping, removed redundent maximum length of payload --- website/s2-connect/discovery-pairing-authentication.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 9d757f4..9286631 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1112,14 +1112,11 @@ The WebSocket Protocol ([RFC6455](https://datatracker.ietf.org/doc/html/rfc6455) RFC 7692 is widely supported by WebSocket libraries and and we are exchanging JSON plain text messages, it is expected to save a large amount of data. Therefore, implementations of S2 WebSockets **SHOULD** support RFC 7692 and **SHOULD** enable it whenever possible. ### Keepalive & heartbeat (ping / pong) - WebSockets by default have a **keepalive** and a **heartbeat mechanism**. Keepalive is designed to keep the connection open while heartbeat is designed to check the latency and check the connection is still working. This means that periodically a ping frame is sent to the server (endpoint) and in response a pong frame is sent. -In order to reduce network traffic, S2 WebSocket implementations **SHOULD** not send ping frames more often than every 50 seconds. Ping & pong frames are control frames and **MAY** include payload of maximum 125 bytes. - -[https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2%5C%5C) - +S2 WebSockets implementations **should** send a ping frame every 30 seconds, and **must not** wait more than 60 seconds between sending ping frames. Ping and pong frames **may** include a payload. +For more details see [RFC6455 Section 5.5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2%5C%5C). ### Termination From e3cea39fc47d99ab94c083665d4e852058a440fa Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 21 May 2026 15:31:53 +0200 Subject: [PATCH 26/67] Specified that the Handshake and HandshakeResponse messages as defined by S2 JSON are not used when using S2 Connect --- website/s2-connect/discovery-pairing-authentication.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 9286631..2860d5c 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1128,7 +1128,9 @@ An S2 session can be terminated in different ways: ## Communication - JSON messages -The S2 standard has been encoded into a JSON schema specification, for details see: [S2 JSON](https://github.com/flexiblepower/s2-ws-json) +S2 Connect uses [S2 JSON](https://github.com/flexiblepower/s2-ws-json) for its JSON schema specification. + +When using S2 Connect, the `Handshake` and `HandshakeResponse` messages (as defined by S2 JSON) **can not** be sent. They are redundant by the pairing and connection initiation process. ### State of communication From 6a53008a4e33b10c3fe7aaa0c6eefdfe09bfee80 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 21 May 2026 15:39:37 +0200 Subject: [PATCH 27/67] Added clarification on HTTPS and specified HTTP version --- website/s2-connect/discovery-pairing-authentication.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 2860d5c..cbfa8b0 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -307,6 +307,10 @@ If the HTTP client does not support any of the provided versions, it means that ### 3. Decide pairing version From the provided list of major versions of the pairing API, the HTTP client **must** select one that is implement by the HTTP client itself (typically the highest supported version). The client **should** select the most recent version. +## HTTPS usage +All HTTP-based interaction in S2 Connect is strictly based on HTTPS (i.e., HTTP over TLS). HTTP (without TLS) **may not** be used for S2 Connect, and it is **recommended** to not run an HTTP (without TLS) server. + +HTTP clients and servers **must** support HTTP version 1.1. It is **recommended** to support more recent version of HTTP as well. # Pairing process (normative) From 470b8b63f4bd3e26396ca70ae040e7524603441a Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 21 May 2026 15:46:45 +0200 Subject: [PATCH 28/67] Specified opening websocket --- website/s2-connect/discovery-pairing-authentication.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index cbfa8b0..331c5c7 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1101,6 +1101,8 @@ The client **must** perform the following checks during this request: If no checks fail the client **should** proceed to the next step. +### Opening the connection +Opening the WebSocket connection is performed by sending a GET request to the URL as provided during the connecting initiation process together with an upgrade request, as specified by [RFC6455](https://datatracker.ietf.org/doc/html/rfc6455). When using HTTP/2, also refer to [RFC8441](https://datatracker.ietf.org/doc/html/rfc8441). ### Authentication For each S2 WebSocket session the client **must** authenticate itself using the commToken in the authorization header of the websocket connection request, following [RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://datatracker.ietf.org/doc/html/rfc6750). From d183b7939b9bbfbfb23044327426e7a8263f1570 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Fri, 22 May 2026 09:32:32 +0200 Subject: [PATCH 29/67] Clarified some details in the challenge response function --- website/s2-connect/discovery-pairing-authentication.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 331c5c7..7b205df 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -480,10 +480,10 @@ Note that the challenge and response are binary data. Both are encoded using Bas The exact function to calculate the response depends on the deployment of the nodes. ``` -When both nodes have a LAN deployment: +When the pairing server is deployed in the LAN: R = HMAC(C, T || F) -When at least one node has a WAN deployment: +When the pairing server is deployed in the WAN: R = HMAC(C, T || D) ``` @@ -491,7 +491,7 @@ Where: | Symbol | Type | Meaning | | ------ | ------- | ---- | | `R` | Binary data | Response | -| `HMAC` | Function | HMAC function with the selected cryptographic hash function | +| `HMAC` | Function | HMAC function for the selected cryptographic hash function. The first argument is the secret key, the second argument is the message. | | `C` | Binary data | Challenge | | `T` | Binary data | Pairing token | | `F` | Binary data | SHA256 fingerprint of the TLS server certificate (i.e. leaf certificate) | From ff646d862defdffb1d7946cfe304a520f252cb53 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Fri, 22 May 2026 15:02:42 +0200 Subject: [PATCH 30/67] Renamed conncetion initiation to session initiaton --- .../discovery-pairing-authentication.md | 59 +++++++++--------- .../connection-initiation.png | Bin 11495 -> 0 bytes .../session-initiation.png | Bin 0 -> 11416 bytes 3 files changed, 30 insertions(+), 29 deletions(-) delete mode 100644 website/static/img/communication-layer/connection-initiation.png create mode 100644 website/static/img/communication-layer/session-initiation.png diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 7b205df..b49dd02 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -22,7 +22,7 @@ This version of this specification is based on the following versions of the und | S2 Connect | OpenAPI files | `v1.0-beta-2` | [Github](https://github.com/flexiblepower/s2-connect) | | S2 JSON | JSON schemas | `v0.02-beta` | [Github](https://github.com/flexiblepower/s2-ws-json) | -> Note: S2 Connect is not directly linked to the version of S2 JSON. The exact version of S2 JSON that is being used by the CEM and RM is negotiated during connection initiation. +> Note: S2 Connect is not directly linked to the version of S2 JSON. The exact version of S2 JSON that is being used by the CEM and RM is negotiated during session initiation. # List of abbreviations @@ -83,9 +83,9 @@ This specification uses the concepts that are defined below. | Term | Definition | | --- | --- | -| Access token | A token that is used for setting up an S2 connection. It is random binary data and must be generated by a cryptographically secure pseudorandom number generator and have a minimum length of 32 bytes. It is encoded using Base64. Also see [11A. Response status 200](#11a-response-status-200). | -| Communication client | The nodes which behaves as the HTTP client when initiating an S2 connection. | -| Communication server | The nodes which behaves as the HTTP server when initiating an S2 connection. | +| Access token | A token that is used for setting up an S2 session. It is random binary data and must be generated by a cryptographically secure pseudorandom number generator and have a minimum length of 32 bytes. It is encoded using Base64. Also see [11A. Response status 200](#11a-response-status-200). | +| Communication client | The nodes which behaves as the HTTP client when initiating an S2 session. | +| Communication server | The nodes which behaves as the HTTP server when initiating an S2 session. | | End user | A person or entity that manages nodes. For the purpose of this specification it is assumed that there is already a trust relationship in place between this person and the nodes. This means that the way the trust relationship has been established is out of scope for this specification. | | End user environment | A restricted area within an application that contains all the nodes that belong to the end user. Also see [Deployment of nodes](#deployment-of-nodes). | | Endpoint | A service which can handle pairing requests or initiates pairing requests itself. An endpoint can represent one node, but could also represent many. | @@ -100,6 +100,7 @@ This specification uses the concepts that are defined below. | Pairing server | The endpoint which behaves as the HTTP server when pairing with an node. | | Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and an node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | +| Session | A stateful exchange of S2 messages between two S2 nodes | | User interface | A user interface through which an end user can interact with an node. Interactions between the end user and the user interface must be secure, but this is out of scope for this specification. Examples of a user interface are a web interface, an app or a physical interface (HMI) on a device. | @@ -137,7 +138,7 @@ Optionally, the initiator node can send a signal to the responder node to indica Once the pairing code is known to the initiator node, the pairing process is started. It is established that both nodes are compatible and it is verified that the entered pairing code is correct. Pairing could either fail or succeed. -If pairing is performed successfully, the CEM and RM instances should establish a connection with each other and communicate through S2. If the connection is interrupted, the instances will automatically try to reestablish the connection. +If pairing is performed successfully, the CEM and RM instances should establish a session with each other and communicate through the exchange of S2 messages. If the connection is interrupted, the instances will automatically try to reestablish the connection. Once a CEM is paired, the user has to possibility to command either of the nodes to *unpair*. After unpairing the CEM and RM instances can no longer communicate through S2 (unless the end user pairs them again). @@ -244,7 +245,7 @@ This document serves as an overall specification of the S2 Connect protocol. How | S2 JSON message structure | The types of S2 messages that can be exchanges between nodes | JSON schema files | [Github](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema) | ## Versioning of OpenAPI files -The pairing API, the connection initiation API and the WAN pairing endpoint registry API are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. All S2 Connect OpenAPI files share the same version number. +The pairing API, the session initiation API and the WAN pairing endpoint registry API are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. All S2 Connect OpenAPI files share the same version number. The minor version is increased when backwards compatible changes are made. Be aware that we consider adding items to certain lists of enums (e.g. the list of supported hash functions) backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. @@ -256,14 +257,14 @@ The major version of the API is embedded in the base URL of the API as `/v[major JSON Schema uses its own versioning scheme, which is based on an `major.minor.patch` scheme. When negotiating the S2 JSON version number, the exact version string **must** be used (e.g. `v1.0.0`). ## Addressing endpoints -The URL of the pairing and connection API are used in the discovery process, pairing process and connection process, as wel as the basis for TLS certificates. +The URL of the pairing and session initiation API are used in the discovery process, pairing process and session initiation process, as wel as the basis for TLS certificates. For **WAN** deployed endpoints, the URL **must** be based on a DNS domain name. For **LAN** deployed endpoints, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instance within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. ## Selecting the version of REST APIs -As explained in the section [Versioning of OpenAPI files](#version) the pairing server, the connection initiation server and the WAN pairing endpoint registry can implement multiple versions of the API specification in parallel. As a result, the client **must** always first determine which version of the API it will use, before it can start interacting with the API. +As explained in the section [Versioning of OpenAPI files](#version) the pairing server, the session initiation server and the WAN pairing endpoint registry can implement multiple versions of the API specification in parallel. As a result, the client **must** always first determine which version of the API it will use, before it can start interacting with the API. The image below depicts the interactions between client and server for the process to determine the API version that will be used. @@ -506,7 +507,7 @@ Once a LAN endpoint has discovered a LAN pairing endpoint (through DNS-SD or by These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the initiator node is the HTTPS client and the responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). -Before the HTTPS client can start interaction with the server, it must first select a version of the API to use. See [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api). For full normative details see the OpenAPI specification files. +Before the HTTPS client can start interaction with the server, it must first select a version of the API to use. See [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis). For full normative details see the OpenAPI specification files. * The client can perform an HTTPS GET request on the path `/endpoint` to receive the remote endpoint details. * The client can perform an HTTPS GET request on the path `/nodes` to receive a list of node details for all the nodes represented by the endpoint. @@ -523,7 +524,7 @@ These operations **must** be implemented by LAN deployed endpoints, but **must n Note that there is no authentication for these operations. It is assumed that these operations are only exposed within the LAN. -Before sending signals the HTTPS client **must** have selected the version of the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-the-pairing-or-connection-initiation-api)). +Before sending signals the HTTPS client **must** have selected the version of the pairing API that will be used (see [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis)). ## Sending the prepare pairing signal @@ -682,7 +683,7 @@ Before two node can be paired, the following preconditions must be met. 1. The HTTP server and the HTTP client can only start with a pairing request when they are fully initialized and have all the details of the nodes it represents available. 2. The HTTP client must have the base URL of the pairing API (e.g. `https://hostname.local/pairing/`) -3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-rest-apis)) +3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) 4. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. > Note: The initiator node could be the HTTP server or the HTTP client @@ -925,11 +926,11 @@ There are four scenarios for CEM and RM deployment, and applying the rules above > Note: A device developed solely for use as an RM in a LAN setup will never function as a communication server. -## Connection initiation +## Session initiation -During the pairing process an `accessToken` is generated by the node which will be the communication server and sent to the node that will be the communication client. This `accessToken` can be used by the communication client to set up a session with the communication server for exchanging S2 messages. The `accessToken` does not expire, but it can only be used (successfully) once to set up a connection. Each time a new connection is made, the `accessToken` will be renewed. The communication server will generate a new `accessToken` and sends it to the communication client. Since this `accessToken` is the only means to connect two nodes once they are paired, the connection initiation process makes sure that both nodes confirm that they have successfully persisted the new `accessToken` before invalidating the old `accessToken`. +During the pairing process an `accessToken` is generated by the node which will be the communication server and sent to the node that will be the communication client. This `accessToken` can be used by the communication client to set up a session with the communication server for exchanging S2 messages. The `accessToken` does not expire, but it can only be used (successfully) once to set up a session. Each time a new session is made, the `accessToken` will be renewed. The communication server will generate a new `accessToken` and sends it to the communication client. Since this `accessToken` is the only means to connect two nodes once they are paired, the session initiation process makes sure that both nodes confirm that they have successfully persisted the new `accessToken` before invalidating the old `accessToken`. -![connection initiation](@site/static/img/communication-layer/connection-initiation.png) +![session initiation](@site/static/img/communication-layer/session-initiation.png)
Image generated using the following PlantUML code: @@ -939,7 +940,7 @@ During the pairing process an `accessToken` is generated by the node which will participant "HTTP Client" as Client participant "HTTP Server" as Server -Client->Server++: 1. POST /[version]/initiateConnection +Client->Server++: 1. POST /[version]/initiateSession Server->Server: 2. Generate new pending accessToken Server-->Client--: 3. Response status 200 Client->Client: 4. Store pending accessToken @@ -954,14 +955,14 @@ Client -> Client : 8. Remove old accessToken ### 0. Precondition -Before an node can initiate a connection, it needs three things. +Before an node can initiate a session, it needs three things. 1. The HTTP server and the HTTP client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. -2. The HTTP client must have the base URL of the connection API (e.g. `https://hostname.local/connection/`) -3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or connection initiation API](#selecting-the-version-of-rest-apis)) +2. The HTTP client must have the base URL of the session initiation API (e.g. `https://hostname.local/connection/`) +3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) 4. The two nodes must have been paired successfully and must have an accessToken for this pairing -If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the connection process. +If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the session initiation process. ### 1. POST /[version]/initiateConnection Since there are situations in which the client cannot know for sure which `accessToken` the communication server uses for this pairing, the communication client must keep a persisted list of `accessTokens` (which will typically contain only one `accessToken`). @@ -1030,7 +1031,7 @@ The client **must** perform the checks in the table below to make sure that it c | Was the selected communication protocol offered in the request? | Do not proceed and try again later with step 1 | ### 4. Store pending accessToken -It client adds the pending `accessToken` to its list of `accessTokens`, but does not yet remove the old one. If the client is not able to persist the pending `accessToken` (e.g. because the storage device or the DBMS is not available), the client does not proceed with the process. Once the client is able to persist `accessTokens` again, it can retry to set up a connection starting with step 1. +It client adds the pending `accessToken` to its list of `accessTokens`, but does not yet remove the old one. If the client is not able to persist the pending `accessToken` (e.g. because the storage device or the DBMS is not available), the client does not proceed with the process. Once the client is able to persist `accessTokens` again, it can retry to set up a session starting with step 1. ### 5. POST /[version]/confirmAccessToken The client confirms to the server that it has successfully persisted the pending `accessToken`. The **pending** `accessToken` is provided through the header of the request. @@ -1039,9 +1040,9 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Do not proceed with connection, try again later | -| If self-signed TLS certificate, check if server is local | Do not proceed with connection, try again later | -| Check if TLS certificate is pinned | Do not proceed with connection, try again later | +| Check TLS certificate | Do not proceed with session, try again later | +| If self-signed TLS certificate, check if server is local | Do not proceed with session, try again later | +| Check if TLS certificate is pinned | Do not proceed with session, try again later | If no checks fail the client **should** proceed. @@ -1049,9 +1050,9 @@ If no checks fail the client **should** proceed. If the provided `accessToken` is in the list pending `accessToken`s, and the token was generated not more than **15 seconds** ago, the server now makes the pending `accessToken` the active `accessToken` for this pairing of nodes (thereby invalidating the old `accessToken`). Also, the entry is removed from the list of pending `accessToken`s. -If the provided `accessToken` is not in the list of pending `accessTokens`s, the server must not accept the connection and respond with status 401. The client can try again later starting at step 1. +If the provided `accessToken` is not in the list of pending `accessTokens`s, the server must not accept the session and respond with status 401. The client can try again later starting at step 1. -If the server is not able to active the new `accessToken` (e.g. because the storage device or the DBMS is not available), the server must not accept the connection and responds with an error code 500. The client can try again later starting at step 1. +If the server is not able to active the new `accessToken` (e.g. because the storage device or the DBMS is not available), the server must not accept the session and responds with an error code 500. The client can try again later starting at step 1. ### 7. Response status 200 @@ -1069,7 +1070,7 @@ Once the communication server has generated a new pending `accessToken`, it must If the communication client doesn't receive a response to confirming the new `accessToken` (step 7), it does not know if the server has activated the new `accessToken`, or if the old `accessToken` is still in place. It now has (at least) two `accessToken`s in its list, and does not know for certain which one is activate at the communication server. It should try all the accessTokens sequentially. If it finds an `accessToken` that is accepted by the communication server, it can remove the other `accessTokens`. ## Reconnection strategy -After connection initiation, the actual S2 communication starts via the selected transport protocol. At some point this connection will terminate. Unless the reason for termination is that the nodes have been unpaired, the communication client **must** try to reconnect with the communication server. In this case the client **must** always start with the connection initiation process (it is not allowed to reconnect using the transport protocol that was selected last time). +After session initiation, the actual S2 communication starts via the selected transport protocol. At some point this session will terminate. Unless the reason for termination is that the nodes have been unpaired, the communication client **must** try to reconnect with the communication server. In this case the client **must** always start with the session initiation process (it is not allowed to reconnect using the transport protocol that was selected last time). An exponential back-off strategy for reconnecting **must** be used, increasing the time between reconnection attempts at every failed attempt. It is recommended to use the following strategy for calculating the delay for the nth attempt to reconnect. The delay time starts when a failed connecting attempt is finished. @@ -1136,7 +1137,7 @@ An S2 session can be terminated in different ways: S2 Connect uses [S2 JSON](https://github.com/flexiblepower/s2-ws-json) for its JSON schema specification. -When using S2 Connect, the `Handshake` and `HandshakeResponse` messages (as defined by S2 JSON) **can not** be sent. They are redundant by the pairing and connection initiation process. +When using S2 Connect, the `Handshake` and `HandshakeResponse` messages (as defined by S2 JSON) **can not** be sent. They are redundant by the pairing and session initiation process. ### State of communication @@ -1181,7 +1182,7 @@ Unpairing can be initiated by either node, and **should** only be done when inst ## Unpairing by the communication client -If the communication client takes the initiative to unpair, it first **should** close the communication session regarding this pairing (if there is one). Then it **must** call the `/[version]/unpair` HTTP API endpoint of the server using its `accessToken` (after discovering the API version at the server, the same way as steps 1 to 3 as mentioned in [Connection initiation](#connection-initiation)). Upon receiving the unpair call from a client, the server node **must** remove all security information related to this pairing. Then the client **must** remove all security information of the communication server related to this pairing. +If the communication client takes the initiative to unpair, it first **should** close the communication session regarding this pairing (if there is one). Then it **must** call the `/[version]/unpair` HTTP API endpoint of the server using its `accessToken` (after discovering the API version at the server, the same way as steps 1 to 3 as mentioned in [Session initiation](#session-initiation)). Upon receiving the unpair call from a client, the server node **must** remove all security information related to this pairing. Then the client **must** remove all security information of the communication server related to this pairing. ## Unpairing by the communication server @@ -1236,7 +1237,7 @@ The `endpoint`, `nodes`, `preparePairing` and `cancelPreparePairing` operations Also when attempting pairing (the `requestPairing` operation) the client **must** accept the self signed certificate. During the pairing attempt process trust is esteblished through a two-sided challenge response mechanism. If the two-sided challenge response succeeds, that means that the client can now trust this node. The client **must** store the self-signed root certificate used by the server. Alternatively, when the pairing server becomes the communication client, the pairing client will send the fingerprint of the self-signed root certificate that the communication server will use (see [6B. POST /[version]/postConnectionDetails](#6b-post-versionpostconnectiondetails)). -When performig connection initiation and unpairing the communication client **must** validate that server cerificate and validate check that the certificate was signed by the self-signed root certificate that was stored in the previous step. +When performig session initiation and unpairing the communication client **must** validate that server cerificate and validate check that the certificate was signed by the self-signed root certificate that was stored in the previous step. ### Updating the certificates A server can update its leaf certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. diff --git a/website/static/img/communication-layer/connection-initiation.png b/website/static/img/communication-layer/connection-initiation.png deleted file mode 100644 index 1ee1b6db3e0c513d0929ddcb06876c111bd79fa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11495 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 Date: Tue, 26 May 2026 14:18:09 +0200 Subject: [PATCH 31/67] Textual improvements and clarifications Co-authored-by: Jorrit Nutma --- .../discovery-pairing-authentication.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index b49dd02..76f3ad4 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -49,7 +49,7 @@ S2 Connect aims to provide a standard method for discovering, pairing and lettin * The **S2 Standard** (formally known as EN50491-12-2) describes the data models and interactions for S2 based communication * The **S2 JSON** project formalizes the data models of the S2 standard into JSON Schemas for a messages based interaction between CEM and RM -Both the S2 Standard ond S2 JSON can be used independently from S2 Connect. +Both the S2 Standard and S2 JSON can be used independently from S2 Connect. ## Requirements S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other. The CEM and RM are logical concepts within the S2 architecture, therefore the S2 standard does not make any assumptions on how and where the CEM and RM are deployed in a real life situation. In practice, the CEM could be deployed on a local gateway in a LAN or as a server somewhere on the internet (WAN), while the RM could be part of the device itself, deployed on an add-on module or on the internet as well. S2 Connect provides a single solution which can be used between devices connected through a LAN, on the internet, or a combination of those. @@ -227,7 +227,7 @@ There are however two situations where this is not possible: * **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that it is not possible to approach a LAN HTTP server from a WAN client. This specifications offers two approaches to this problem: * Accept this limitation and not allow the WAN node to be the initiator node. Pairing can only be performed when the LAN node is the initiator node and the WAN node is the responder node. Special care must be taken to explain this to the end user. - * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTP server in the cloud, even though the node itself is in the WAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients. There must always be a method for purely LAN based pairing. + * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTP server in the cloud, even though the node itself is in the LAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients and must not be used by LAN clients. There must always be a method for purely LAN based pairing. * **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTP server is far more memory intensive than an HTTP client, there is an option to implement a LAN RM instance purely as an HTTP client. A long-polling mechanism is available to indicate to the HTTP Server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTP server. In other words: in this specific situation the initiator node behaves as the HTTP server, and the responder node only has to be an HTTP client. ![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) @@ -959,12 +959,12 @@ Before an node can initiate a session, it needs three things. 1. The HTTP server and the HTTP client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. 2. The HTTP client must have the base URL of the session initiation API (e.g. `https://hostname.local/connection/`) -3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) +3. The HTTP client must have selected the version on the session initiation API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) 4. The two nodes must have been paired successfully and must have an accessToken for this pairing If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the session initiation process. -### 1. POST /[version]/initiateConnection +### 1. POST /[version]/initiateSession Since there are situations in which the client cannot know for sure which `accessToken` the communication server uses for this pairing, the communication client must keep a persisted list of `accessTokens` (which will typically contain only one `accessToken`). The client **must** perform the following checks during this request: @@ -999,7 +999,7 @@ The server **must** perform the checks in the table below to make sure that it c | Is this the correct `accessToken` for this node ID? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | | Is there overlap between the communication protocols? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleCommunicationProtocols` | Retry later | | Is there overlap between the S2 message versions? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleS2MessageVersions` | Retry later | -| Are the endpoint and node ready for pairing? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | +| Are the endpoint and node ready for connecting? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | | Is this is a WAN pairing server for a LAN endpoint, does the client have a WAN deployment? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | ### 2. Generate new pending `accessToken` @@ -1031,7 +1031,7 @@ The client **must** perform the checks in the table below to make sure that it c | Was the selected communication protocol offered in the request? | Do not proceed and try again later with step 1 | ### 4. Store pending accessToken -It client adds the pending `accessToken` to its list of `accessTokens`, but does not yet remove the old one. If the client is not able to persist the pending `accessToken` (e.g. because the storage device or the DBMS is not available), the client does not proceed with the process. Once the client is able to persist `accessTokens` again, it can retry to set up a session starting with step 1. +The client adds the pending `accessToken` to its list of `accessTokens`, but does not yet remove the old one. If the client is not able to persist the pending `accessToken` (e.g. because the storage device or the DBMS is not available), the client does not proceed with the process. Once the client is able to persist `accessTokens` again, it can retry to set up a session starting with step 1. ### 5. POST /[version]/confirmAccessToken The client confirms to the server that it has successfully persisted the pending `accessToken`. The **pending** `accessToken` is provided through the header of the request. @@ -1235,9 +1235,9 @@ LAN deployed nodes will have a self-signed root certificate, and a server (leaf) The `endpoint`, `nodes`, `preparePairing` and `cancelPreparePairing` operations can be called before the pairing has happened. For these operations the client **must** accept the self-signed certificates. -Also when attempting pairing (the `requestPairing` operation) the client **must** accept the self signed certificate. During the pairing attempt process trust is esteblished through a two-sided challenge response mechanism. If the two-sided challenge response succeeds, that means that the client can now trust this node. The client **must** store the self-signed root certificate used by the server. Alternatively, when the pairing server becomes the communication client, the pairing client will send the fingerprint of the self-signed root certificate that the communication server will use (see [6B. POST /[version]/postConnectionDetails](#6b-post-versionpostconnectiondetails)). +Also when attempting pairing (the `requestPairing` operation) the client **must** accept the self-signed certificate. During the pairing process trust is established through a two-sided challenge response mechanism. If the two-sided challenge response succeeds, that means that the client can now trust the server of the node it is paired with. The client **must** store the self-signed root certificate used by the server. Alternatively, when the pairing server becomes the communication client, the pairing client will send the fingerprint of the self-signed root certificate that the communication server will use (see [6B. POST /[version]/postConnectionDetails](#6b-post-versionpostconnectiondetails)). -When performig session initiation and unpairing the communication client **must** validate that server cerificate and validate check that the certificate was signed by the self-signed root certificate that was stored in the previous step. +When performig session initiation and unpairing the communication client **must** validate that server cerificate and check that the certificate was signed by the self-signed root certificate that was stored in the previous step. ### Updating the certificates A server can update its leaf certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. From 241fb2694a8f33cdcaf26720534a5b3ac6d32817 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 26 May 2026 14:28:59 +0200 Subject: [PATCH 32/67] Moved the check for WAN pairing server for LAN nodes to the correct location --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 76f3ad4..aef7e0c 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -734,6 +734,7 @@ The server **must** perform the checks in the table below to make sure that it c | Is there overlap between the S2 message versions? | `IncompatibleS2MessageVersions` | Yes | | If the targeted node on the HTTP server is the initiator node, did the end user provide a valid pairing token? | `NoValidPairingTokenOnPairingServer` | No | | If the targeted node on the HTTP server is the responder node, does the node have a pairing token which has not expired? | `NoValidPairingTokenOnPairingServer` | No | +| Is this is a WAN pairing server for a LAN endpoint, does the client have a WAN deployment? | `Other` | No | > Note: If the node that is being paired is an RM which is already paired, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation must be unpaired. @@ -1000,7 +1001,6 @@ The server **must** perform the checks in the table below to make sure that it c | Is there overlap between the communication protocols? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleCommunicationProtocols` | Retry later | | Is there overlap between the S2 message versions? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleS2MessageVersions` | Retry later | | Are the endpoint and node ready for connecting? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | -| Is this is a WAN pairing server for a LAN endpoint, does the client have a WAN deployment? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | ### 2. Generate new pending `accessToken` From d51493b07e87394928ee106818c0c738c419d840 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Wed, 27 May 2026 10:21:05 +0200 Subject: [PATCH 33/67] Enhance S2-Connect specification details --- website/docs/specs/specs.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/specs/specs.md b/website/docs/specs/specs.md index e4daa0c..ad5416e 100644 --- a/website/docs/specs/specs.md +++ b/website/docs/specs/specs.md @@ -15,4 +15,6 @@ The json-schema can be found here: [s2-json](https://github.com/flexiblepower/s2 The data model is also represented on this documentation website, see [Reference](/model-reference). ## S2-Connect -The S2-Connect specification allows for full stack interoperable S2 solutions. It specifies how S2 appliances can discovery each other, how they pair and which communication protocol to use. The specification is published as open-access [here](/s2-connect/discovery-pairing-authentication). \ No newline at end of file +S2 Connect is a protocol specification enabling communication between an Energy Management System (EMS) and an Energy Smart Appliance (ESA) based on the S2 (EN50491‑12‑2) standard. It defines how devices discover, pair, communicate and unpair across local, cloud, and hybrid environments. It allows for a secure and fully interoperable solution between EMS and ESA. + +[The specification](/s2-connect/discovery-pairing-authentication) is published as open-access. From a572af466902a113fad294332ce1fb4ed0f29581 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Wed, 27 May 2026 11:13:31 +0200 Subject: [PATCH 34/67] fix two typos --- website/s2-connect/discovery-pairing-authentication.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index aef7e0c..cf11094 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -177,7 +177,7 @@ The endpoint is basically the application that hosts the nodes. In a LAN deploym An S2 connection basically consists of four steps: discovery, pairing, communication and unpairing. For these steps different types of technology are used. -On of the main technologies the process relies on is HTTP REST. All interactions based on HTTP are formally described in OpenAPI specification files. [OpenAPI](https://swagger.io/specification/) is a formal language for specifying HTTP based API's. It can be used to generate reference documentation for developers, as well as stub code for many programming languages. +One of the main technologies the process relies on is HTTP REST. All interactions based on HTTP are formally described in OpenAPI specification files. [OpenAPI](https://swagger.io/specification/) is a formal language for specifying HTTP based API's. It can be used to generate reference documentation for developers, as well as stub code for many programming languages. ### Discovery @@ -540,7 +540,7 @@ The client **must** send the following information in the request. For full norm | --- | --- | | `clientNodeDescription` | Details of the node at the client that the end user intents to pair with the node at the server | | `clientEndpointDescription` | Details of the client endpoint | -| `serverNodeId` | The node ID of the node at the server that the end user intents to pair weth the node at the client (see [Getting endpoint information](#getting-endpoint-information) for details on how to retrieve the server node ID) | +| `serverNodeId` | The node ID of the node at the server that the end user intents to pair with the node at the client (see [Getting endpoint information](#getting-endpoint-information) for details on how to retrieve the server node ID) | The server **must** perform the checks in the table below. For the checks with HTTP status 400, a `PairingResponseErrorMessage` must be send. In that case, the contents of the `additionalInfo` field is supposed the be helpful and up to the implementer. From 381485b99b4f299d6918bd5247903b34f2d5a280 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 28 May 2026 10:44:11 +0200 Subject: [PATCH 35/67] Add certificate check and remove mentions of local server checks --- .../discovery-pairing-authentication.md | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index aef7e0c..387826c 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -294,9 +294,9 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | -| Store fingerprint of TLS certificate for later check | | +| [Check TLS certificate](#certificate-validation) (with exemption of authenticity) | Pairing is failed, do not proceed with the pairing attempt | + +The fingerprint of TLS certificate must be stored for later check. If no checks fail the client **should** proceed to the next step. @@ -532,7 +532,7 @@ The client can send the prepare pairing signal to the server by sending an HTTPS | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Sending signal failed, do not proceed with sending signal | +| [Check TLS certificate](#certificate-validation) | Sending signal failed, do not proceed with sending signal | The client **must** send the following information in the request. For full normative details see the OpenAPI specification files. @@ -559,7 +559,7 @@ If the client sent a prepare pairing signal the the server, and the end user has | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Sending signal failed, do not proceed with sending signal | +| [Check TLS certificate](#certificate-validation) | Sending signal failed, do not proceed with sending signal | The client **must** send the following information in the request. For full normative details see the OpenAPI specification files. @@ -713,8 +713,7 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | +| [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | | Check if same fingerprint is used as previous request (when applicable) | Pairing is failed, do not proceed with the pairing attempt | If no checks fail the client **should** proceed to the next step. @@ -801,8 +800,7 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | +| [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | | Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | If no checks fail the client **should** proceed to the next step. @@ -843,8 +841,7 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | +| [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | | Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | If no checks fail the client **should** proceed to the next step. @@ -875,9 +872,9 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | -| Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | +| [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | + If no checks fail the client **should** proceed to the next step. @@ -956,7 +953,7 @@ Client -> Client : 8. Remove old accessToken ### 0. Precondition -Before an node can initiate a session, it needs three things. +Before an node can initiate a session, it needs four things. 1. The HTTP server and the HTTP client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. 2. The HTTP client must have the base URL of the session initiation API (e.g. `https://hostname.local/connection/`) @@ -972,9 +969,8 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Initiation is failed, do not proceed with the initiation attempt | -| If self-signed TLS certificate, check if server is local | Initiation is failed, do not proceed with the initiation attempt | -| Check if TLS certificate is pinned | Initiation is failed, do not proceed with the initiation attempt | +| [Check TLS certificate](#certificate-validation) | Initiation is failed, do not proceed with the initiation attempt | +| If self-signed TLS certificate, check if TLS certificate is pinned | Initiation is failed, do not proceed with the initiation attempt | If no checks fail the client **should** proceed to the next step. @@ -1040,9 +1036,9 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Do not proceed with session, try again later | -| If self-signed TLS certificate, check if server is local | Do not proceed with session, try again later | -| Check if TLS certificate is pinned | Do not proceed with session, try again later | +| [Check TLS certificate](#certificate-validation) | Do not proceed with session, try again later | +| If self-signed TLS certificate, check if certificate is pinned | Do not proceed with session, try again later | + If no checks fail the client **should** proceed. @@ -1096,9 +1092,8 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | -| Check TLS certificate | Websocket connection failed, do not proceed with the connection attempt | -| If self-signed TLS certificate, check if server is local | Websocket connection failed, do not proceed with the connection attempt | -| Check if TLS certificate is pinned | Websocket connection failed, do not proceed with the connection attempt | +| [Check TLS certificate](#certificate-validation) | Websocket connection failed, do not proceed with the connection attempt | +| If self-signed TLS certificate, check if certificate is pinned | Websocket connection failed, do not proceed with the connection attempt | If no checks fail the client **should** proceed to the next step. @@ -1233,17 +1228,27 @@ SelfSignedCA --> LocalServerCertificate ### Trusting a self-signed root certificate LAN deployed nodes will have a self-signed root certificate, and a server (leaf) certificate which is signed by the self-signed root certificate. -The `endpoint`, `nodes`, `preparePairing` and `cancelPreparePairing` operations can be called before the pairing has happened. For these operations the client **must** accept the self-signed certificates. +The `endpoint`, `nodes`, `preparePairing` and `cancelPreparePairing` operations can be called before the pairing has happened. For these operations the client **must** accept the self-signed certificates, even though it cannot trust the root certificate. Also when attempting pairing (the `requestPairing` operation) the client **must** accept the self-signed certificate. During the pairing process trust is established through a two-sided challenge response mechanism. If the two-sided challenge response succeeds, that means that the client can now trust the server of the node it is paired with. The client **must** store the self-signed root certificate used by the server. Alternatively, when the pairing server becomes the communication client, the pairing client will send the fingerprint of the self-signed root certificate that the communication server will use (see [6B. POST /[version]/postConnectionDetails](#6b-post-versionpostconnectiondetails)). -When performig session initiation and unpairing the communication client **must** validate that server cerificate and check that the certificate was signed by the self-signed root certificate that was stored in the previous step. +When performing session initiation and unpairing the communication client **must** validate that server certificate and check that the certificate was signed by the self-signed root certificate that was stored in the previous step. ### Updating the certificates A server can update its leaf certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. If the server is deployed in the LAN, and thus uses a self-signed root certificate, the root certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server (or the device that hosts the server). If the used crypto for the the CA certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its leaf certificate at least once every 6 months. +### Certificate validation +Certificate checks are mentioned several times in this specification. The check consists of these parts: +- Authenticity: In case of a WAN server, is the certificate issued by a trusted CA (using the chain of trust)? In case of a LAN server, is the root certificate the same as during the pairing? +- Domain name validation: has the certificate been issued for the (local) domain name of the server? +- Expiration data: has the certificate not been expired? +- Integrity: Has the certificate not been tampered with? This is verified by checking whether the signature is valid. +- Security check: has an allowed crypto algorithm been used? See the section on [Cipher suites](#cipher-suites). + +With the exemption of some cases where the authenticity cannot be verified yet, all checks must be performed every time a TLS connection is set up. + ## Cipher suites Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. From bfa7e221dfbd484b4be93798f0a46247d359926d Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 28 May 2026 11:23:13 +0200 Subject: [PATCH 36/67] Update website/s2-connect/discovery-pairing-authentication.md --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 387826c..34fd6d7 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1245,7 +1245,7 @@ Certificate checks are mentioned several times in this specification. The check - Domain name validation: has the certificate been issued for the (local) domain name of the server? - Expiration data: has the certificate not been expired? - Integrity: Has the certificate not been tampered with? This is verified by checking whether the signature is valid. -- Security check: has an allowed crypto algorithm been used? See the section on [Cipher suites](#cipher-suites). +- Cryptography check: has an allowed crypto algorithm been used? See the section on [Cipher suites](#cipher-suites). With the exemption of some cases where the authenticity cannot be verified yet, all checks must be performed every time a TLS connection is set up. From d52f51f55c9b13d1bedc37565792b2d5a8672e1f Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 28 May 2026 11:26:26 +0200 Subject: [PATCH 37/67] Improve several mentions of certificate checks --- website/s2-connect/discovery-pairing-authentication.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 387826c..dfb0abe 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -801,7 +801,7 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | -| Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | If no checks fail the client **should** proceed to the next step. @@ -842,7 +842,7 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | -| Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | If no checks fail the client **should** proceed to the next step. @@ -873,7 +873,7 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | +| If self-signed TLS certificate, check if certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | If no checks fail the client **should** proceed to the next step. @@ -970,7 +970,7 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Initiation is failed, do not proceed with the initiation attempt | -| If self-signed TLS certificate, check if TLS certificate is pinned | Initiation is failed, do not proceed with the initiation attempt | +| If self-signed TLS certificate, check if certificate is pinned | Initiation is failed, do not proceed with the initiation attempt | If no checks fail the client **should** proceed to the next step. From 878376aa7b7a49684c71c114e4377af283a2f36a Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 28 May 2026 11:35:16 +0200 Subject: [PATCH 38/67] Improve local server authenticity check description --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 2341f2a..26bdc2e 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1241,7 +1241,7 @@ If the server is deployed in the LAN, and thus uses a self-signed root certifica ### Certificate validation Certificate checks are mentioned several times in this specification. The check consists of these parts: -- Authenticity: In case of a WAN server, is the certificate issued by a trusted CA (using the chain of trust)? In case of a LAN server, is the root certificate the same as during the pairing? +- Authenticity: In case of a WAN server, is the certificate issued by a trusted CA (using the chain of trust)? For a LAN server, is the root certificate pinned to the server’s domain name? - Domain name validation: has the certificate been issued for the (local) domain name of the server? - Expiration data: has the certificate not been expired? - Integrity: Has the certificate not been tampered with? This is verified by checking whether the signature is valid. From a4c355229904935b0a474ff0dbc187f38d1a7e9c Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 28 May 2026 11:53:09 +0200 Subject: [PATCH 39/67] fix typos --- .../discovery-pairing-authentication.md | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index cf11094..4be6fee 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -9,7 +9,7 @@ sidebar_position: 2 S2 Connect is a JSON based protocol specification implementing the EN50491-12-2 "S2" standard for home and building energy management. -This specification addresses everything needed to created a secure and interoperable implementation of the S2 standard. The communication layer concerns the discovery, the pairing, the application layer communication protocol, the authentication, the message data model and the serialization. It was specifically designed to support multiple deployment scenario's and to give a relative consistent user experience throughout these different scenario's. +This specification addresses everything needed to create a secure and interoperable implementation of the S2 standard. The communication layer concerns the discovery, the pairing, the application layer communication protocol, the authentication, the message data model and the serialization. It was specifically designed to support multiple deployment scenarios and to give a relative consistent user experience throughout these different scenarios. The protocol is designed to specify communication between two devices, a resource (e.g. a heat pump or EV charger) and a (home) energy management system. It is worth noting that, while this specification focuses on describing the interaction between two components, a (home) energy management is likely to be communicating with multiple resources at the same time. @@ -91,17 +91,17 @@ This specification uses the concepts that are defined below. | Endpoint | A service which can handle pairing requests or initiates pairing requests itself. An endpoint can represent one node, but could also represent many. | | Initiator node | The node that takes the initiative to pair with a responder node. This is typically the node from which the user initiates the pairing process. It is the counterpart of the responder node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | | Node | Refers to an instance of either a CEM or a RM as defined in EN 50491-12-1 and implementing this specification. S2 communication between two nodes can only be established if one of the nodes is a CEM and the other a RM. These nodes must also have the same end user. | -| Node ID | A globally unique identifier for an node in the UUID format. | -| Node ID alias | A short identifier for an node, which is unique in the context of a single endpoint. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Node ID | A globally unique identifier for a node in the UUID format. | +| Node ID alias | A short identifier for a node, which is unique in the context of a single endpoint. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Pairing attempt | The process of pairing two nodes. The process can be completed successfully or unsuccessfully. | -| Pairing client | The endpoint which behaves as the HTTP client when pairing with an node. | +| Pairing client | The endpoint which behaves as the HTTP client when pairing with a node. | | Pairing code | The pairing code is the string of characters the end user has to copy from the responder node user interface to the initiator node user interface, in order to pair the two nodes. The pairing code consists of a pairing token and if required a node ID alias. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Pairing endpoint registry | The central registry that keeps track of publicly available pairing servers. | -| Pairing server | The endpoint which behaves as the HTTP server when pairing with an node. | -| Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and an node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Pairing server | The endpoint which behaves as the HTTP server when pairing with a node. | +| Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and a node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | | Session | A stateful exchange of S2 messages between two S2 nodes | -| User interface | A user interface through which an end user can interact with an node. Interactions between the end user and the user interface must be secure, but this is out of scope for this specification. Examples of a user interface are a web interface, an app or a physical interface (HMI) on a device. | +| User interface | A user interface through which an end user can interact with a node. Interactions between the end user and the user interface must be secure, but this is out of scope for this specification. Examples of a user interface are a web interface, an app or a physical interface (HMI) on a device. | # Architecture (informative) @@ -109,12 +109,12 @@ This section explains the overall architecture and deployment options for CEM an ## Deployment of nodes -This specification is concerned with connecting an instance of a CEM with an instance of a RM. Either of these instances are referred to as *nodes*, which either have the CEM *role* or the RM *role*. Obviously, it is only possible to pair an node with the CEM role to an node with the RM role. +This specification is concerned with connecting an instance of a CEM with an instance of a RM. Either of these instances are referred to as *nodes*, which either have the CEM *role* or the RM *role*. Obviously, it is only possible to pair a node with the CEM role to a node with the RM role. Nodes can be deployed locally within the LAN, or somewhere on a server in the WAN. Although their deployment doesn't significantly affect the working of these nodes, there are some key differences between these types of deployment. * **WAN** nodes are typically part of a large application that run on many servers and/or on some kind of cloud computing platform. A single application usually serves many users. Each user could have one or multiple nodes. This could for example be a cloud-based energy management system that can connect to many devices. It could also be a cloud environment of a device manufacturer that hosts the RM instances in the cloud. An end user could own multiple devices from this manufacturer, thus the application could host multiple RM instances for this particular user. We call a group of nodes that a single user can manage within one application an *end user environment*. It is also possible that an end user environment contains both CEM and RM instances. The user interface is typically a web interface or a smartphone app. -* **LAN** nodes are typically part of an application that runs on an embedded computer device somewhere in the building. Such a device could be a physical energy management system, an energy flexible device such as a home battery, heat pump or EV charger, or a gateway device which connects to an energy flexible device through some kind of protocol. Often an application will only host a single node, but it is also possible that an application hosts multiple nodes. A device could function completely on its own, but it cloud also be connected to an internet based application of the manufacturer. The user interface could be a physical human-machine interface on the device, but also be a smartphone app that connects directly to the device (e.g. via bluetooth), or a smartphone app or web interface that connects to an internet based application of the manufacturer. For energy flexible devices, it is assumed that they could also have no user interface at all, or that they are very constrained when it comes to computing power. It is assumed that a CEM always has a user interface. +* **LAN** nodes are typically part of an application that runs on an embedded computer device somewhere in the building. Such a device could be a physical energy management system, an energy flexible device such as a home battery, heat pump or EV charger, or a gateway device which connects to an energy flexible device through some kind of protocol. Often an application will only host a single node, but it is also possible that an application hosts multiple nodes. A device could function completely on its own, but it could also be connected to an internet based application of the manufacturer. The user interface could be a physical human-machine interface on the device, but also be a smartphone app that connects directly to the device (e.g. via Bluetooth), or a smartphone app or web interface that connects to an internet based application of the manufacturer. For energy flexible devices, it is assumed that they could also have no user interface at all, or that they are very constrained when it comes to computing power. It is assumed that a CEM always has a user interface. ![Deployment_options](@site/static/img/communication-layer/deployment_options.png) @@ -169,7 +169,7 @@ iui->e: Pairing result (success or failure) Within this protocol we make a clear distinction between two types of identities: the one of the *endpoint* and the one of the *node* itself. -An node is an instance of a CEM or a RM. It is either a specific energy management system or service, or the representative of a physical energy flexible device, such as a heat pump or a home battery. It is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. +a node is an instance of a CEM or a RM. It is either a specific energy management system or service, or the representative of a physical energy flexible device, such as a heat pump or a home battery. It is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. The endpoint is basically the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. @@ -184,7 +184,7 @@ One of the main technologies the process relies on is HTTP REST. All interaction The first step is finding the responder node from the initiator node. In principle this is done based on the URL of the responder node. However, to improve user experience, two systems exist to find this URL in a more user friendly manner. For more details see [Discovery](#discovery). * If the responder node is deployed in the WAN, the end user can find the endpoint through the pairing endpoint registry. This would result in a list of vendors that offer nodes. -* If both nodes are deployed in the LAN however, the responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. This process can also be used when an node is deployed in the WAN, but the device also has a presence in the LAN. +* If both nodes are deployed in the LAN however, the responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. This process can also be used when a node is deployed in the WAN, but the device also has a presence in the LAN. ### Pairing @@ -202,7 +202,7 @@ The process always starts with HTTP based communication, but then is handed over We'll refer to the endpoint that behaves as the HTTP server during the communication process as the *communication server*, and the client as the *communication client*. -It should be noted that pairing and communication are two separate HTTP interfaces, that don't have to be used in the same way. It could be that an node is an pairing client, but then becomes a communication server. This depends on the deployment of the nodes (see [Pairing details for different deployments](#pairing-details-for-different-deployments)). +It should be noted that pairing and communication are two separate HTTP interfaces, that don't have to be used in the same way. It could be that a node is an pairing client, but then becomes a communication server. This depends on the deployment of the nodes (see [Pairing details for different deployments](#pairing-details-for-different-deployments)). Communication interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) @@ -330,7 +330,7 @@ For each pairing attempt, one endpoint must be the HTTP server, while the other > A LAN deployed RM implementation can choose if it implements the HTTP server, or that it implements the HTTP client and uses long-polling. This feature exists to accommodate RM implementations with constrained hardware. -A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. An node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). +A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. a node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). ## Discovery @@ -419,7 +419,7 @@ The receiver of the service description **must** use the URL provided in the TXT > > `avahi-browse -r _s2connect._tcp` > -> Registering an endpoint could for example be done using the folling avahi command: +> Registering an endpoint could for example be done using the following avahi command: > > `avahi-publish-service -s "EVSE1038" _s2connect._tcp 443 "txtvers=1" "e_name=brand" "deployment=LAN" "pairingUrl=https://EVSE1038.local:443/pairing/" --sub _rm._sub._s2connect._tcp` @@ -434,7 +434,7 @@ The pairing token may consist out of lower case letters, upper case letters and | Dynamic pairing token | 4 characters | Limited duration, 5 minutes is recommended | `^[0-9a-zA-Z]{4,}$` | | Static pairing token | 6 characters | Indefinitely | `^[0-9a-zA-Z]{6,}$` | -An endpoint can host multiple nodes. When attempting to pair a certain node, the endpoint needs to know exactly which of its nodes this pairing attempt is aimed at. Nodes are uniquely identified with their node ID. Since this node ID is a UUID, it is pretty long and cumbersome to type in. That is why an endpoint can assign its nodes a *node ID alias*. This is an identifier that is intended to be short, and only unique within the context of this particular endpoint. Node ID aliases could be assigned by the endpoint whenever new nodes are created, but also could be generated dynamically only when someone is attempting to pair to this node. This way, node ID aliases have a short live, and can be reused by other nodes at other moments. This allows to use shorter node ID aliases. Node ID aliases are a string of characters, which may include lower case letters, upper case letters and numbers. Node ID aliases are ideally as short as possible (at least one character), but should of course be long enough to allow the endpoint to uniquely identify an node. When an endpoint only contains one node, there is no need for a node ID alias. +An endpoint can host multiple nodes. When attempting to pair a certain node, the endpoint needs to know exactly which of its nodes this pairing attempt is aimed at. Nodes are uniquely identified with their node ID. Since this node ID is a UUID, it is pretty long and cumbersome to type in. That is why an endpoint can assign its nodes a *node ID alias*. This is an identifier that is intended to be short, and only unique within the context of this particular endpoint. Node ID aliases could be assigned by the endpoint whenever new nodes are created, but also could be generated dynamically only when someone is attempting to pair to this node. This way, node ID aliases have a short live, and can be reused by other nodes at other moments. This allows to use shorter node ID aliases. Node ID aliases are a string of characters, which may include lower case letters, upper case letters and numbers. Node ID aliases are ideally as short as possible (at least one character), but should of course be long enough to allow the endpoint to uniquely identify a node. When an endpoint only contains one node, there is no need for a node ID alias. The **node ID alias** can be validated with the following regular expression: @@ -474,7 +474,7 @@ The algorithm to calculate the response is based on the HMAC (hash-based message The HMAC function itself uses a cryptographic hash function for its calculations. Since cryptographic hash functions might contain vulnerabilities, this protocol uses a simple selection mechanism for the cryptographic hash function. The HTTP client sends with the requestPairing HTTP request a list of supported hash functions. In the response the HTTP server indicates which hash function it has selected from this list. This function **must** be used for all response calculations during het pairing attempt. Currently there is only one hash function available (SHA256), but other options might be added in the future. -It order to avoid man-in-the-middle attacks, information about the connection in also used as input for calculating the response. When both nodes are deployed in the LAN, the SHA256 fingerprint of the server certificate is used. In other scenario's, the domain name of the server is included in the calculation of the response. +It order to avoid man-in-the-middle attacks, information about the connection in also used as input for calculating the response. When both nodes are deployed in the LAN, the SHA256 fingerprint of the server certificate is used. In other scenarios, the domain name of the server is included in the calculation of the response. Note that the challenge and response are binary data. Both are encoded using Base64 and must also be decoded before they can be used. SHA256 certificate fingerprints are encoded into a hexadecimal string, and must be decoded as hexadecimal string before it can be used as input (note that fingerprint strings usually contain colons to separate bytes). The pairing token and domain name are strings, which need to be converted into binary data using the ASCII table. @@ -565,8 +565,8 @@ The client **must** send the following information in the request. For full norm | Property | Description | | --- | --- | -| `clientNodeId` | The node ID of the node at the client that the end user intents to pair weth the node at the server | -| `serverNodeId` | The node ID of the node at the server that the end user intents to pair weth the node at the client | +| `clientNodeId` | The node ID of the node at the client that the end user intents to pair with the node at the server | +| `serverNodeId` | The node ID of the node at the server that the end user intents to pair wth the node at the client | The server **should** respond with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). @@ -616,11 +616,11 @@ The table below indicates how the client should respond to the requests of the s | 401 | n/a | n/a | Stop long-polling, do not attempt long-polling with this node again | n/a | | 500 | n/a | n/a | Wait before trying to send the next request | Only the `clientNodeId`| -2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user inters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTP server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTP client). +2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTP server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTP client). When the server sends the `requestPairing` action, the node on the client must already have issued a pairing token. If the node uses a dynamic pairing code, it could be the case that the pairing code has expired, or that no pairing code has been issued in the first place. In that case the client **must** perform a new request with an `errorMessage` containing the value `NoValidTokenOnPairingClient` in the object associated with the node ID of the node should have attempted to pair. -The activity diagram below summarizes the complete long-polling process from teh perspective of the client. +The activity diagram below summarizes the complete long-polling process from the perspective of the client. ![image](@site/static/img/communication-layer/long-polling_activity_diagram.png) @@ -784,7 +784,7 @@ Note that in case of a local server, the TLS certificate fingerprint is part of ### 5. Calculate serverHmacChallengeResponse The HTTP client calculates a response to the provided `serverHmacChallenge` using the hashing algorithm as indicated in the `selectedHmacHashingAlgorithm`. For details see [Challenge response process](#challenge-response-process). -From hereon the process branches into two scenario's, depending on if the HTTP client will be the communication client or the communication server. See [Mapping the CEM and RM to communication server or client](#mapping-the-cem-and-rm-to-communication-server-or-client) for which node will perform which role for communication. +From hereon the process branches into two scenarios, depending on if the HTTP client will be the communication client or the communication server. See [Mapping the CEM and RM to communication server or client](#mapping-the-cem-and-rm-to-communication-server-or-client) for which node will perform which role for communication. If the HTTP server will be the communication *server* steps 6A, 7A and 8A **should** follow. If the HTTP server will be the communications *client* steps 6B, 7B en 8B **should** follow. @@ -956,7 +956,7 @@ Client -> Client : 8. Remove old accessToken ### 0. Precondition -Before an node can initiate a session, it needs three things. +Before a node can initiate a session, it needs three things. 1. The HTTP server and the HTTP client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. 2. The HTTP client must have the base URL of the session initiation API (e.g. `https://hostname.local/connection/`) @@ -1129,8 +1129,8 @@ For more details see [RFC6455 Section 5.5.2](https://datatracker.ietf.org/doc/ht An S2 session can be terminated in different ways: -* In case an node unexpectedly becomes unavailable, the WebSocket connection **CAN** timeout. This will cause an S2 session to be terminated. More details about the timeout can be found [in the heartbeat section](#keepalive--heartbeat-ping--pong) -* an node **CAN** terminate the S2 session by sending the S2 terminate message, including an optional earliest time that the session can be restored. The other node can take this into account in planning and (in the case of a client) deciding when to attempt to reconnect. +* In case a node unexpectedly becomes unavailable, the WebSocket connection **CAN** timeout. This will cause an S2 session to be terminated. More details about the timeout can be found [in the heartbeat section](#keepalive--heartbeat-ping--pong) +* a node **CAN** terminate the S2 session by sending the S2 terminate message, including an optional earliest time that the session can be restored. The other node can take this into account in planning and (in the case of a client) deciding when to attempt to reconnect. * After two nodes have unpaired, the S2 WebSocket connection **MUST** be terminated immediately. ## Communication - JSON messages @@ -1171,7 +1171,7 @@ WebSocketDisconnected --> [*] | --- | --- | --- | | WebSocket Connected | SelectControlType
SessionRequest
ReceptionStatus | ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | | ControlType PEBC activated | PEBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | PEBC.EnergyConstraint
PEBC.PowerConstraint
RevokeObject InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | -| ControlType PPBC activated | PPBC.EndInterruptionInstruction
PPBC.ScheduleInstruction
PPBC.StartInterruptionInstruction
SelectControlType
SessionRequest
ReceptionStatus | PPBC.PowerProfileDefinition
PPBC.PowerPorfileStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType PPBC activated | PPBC.EndInterruptionInstruction
PPBC.ScheduleInstruction
PPBC.StartInterruptionInstruction
SelectControlType
SessionRequest
ReceptionStatus | PPBC.PowerProfileDefinition
PPBC.PowerProfileStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | | ControlType OMBC activated | OMBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | OMBC.Status
OMBC.SystemDescription
OMBC.TimerStatus RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | | ControlType FRBC activated | FRBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | FRBC.ActuatorStatus
FRBC.FillLevelTargetProfile
FRBC.LeakageBehaviour
FRBC.StorageStatus
FRBC.SystemDescription
FRBC.UsageForecast
FRBC.TimerStatus
RevokeObject
InstructionStatusUpdate ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | | ControlType DDBC activated | DDBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | DDBC.ActuatorStatus
DDBC.AverageDemandRateForecast
DDBC.SystemDescription
DDBC.TimerStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement PowerForecast
SessionRequest
ReceptionStatus | @@ -1196,7 +1196,7 @@ Client and server **can** keep other (non-security) information for, for example To prevent brute-force pairing request, the server **must** implement rate limiting on the requestPairing endpoint. For more details see [2. Calculate clientHmacChallengeResponse](#2-calculate-clienthmacchallengeresponse). ## DDoS countermeasures -Especilly WAN endpoints are vulnerable for DDoS attacks. It is recommended for WAN endpoints to take countermeasures against these attacks. The nature of these countermeasures are outside of the scope of this spefication. +Especially WAN endpoints are vulnerable for DDoS attacks. It is recommended for WAN endpoints to take countermeasures against these attacks. The nature of these countermeasures are outside of the scope of this specification. ## TLS Certificates All HTTP and WebSocket communication uses TCP over TLS with server certificates. The server certificates **must** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. From 1e552fae01595013483200bcab7fafb576876d9a Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 28 May 2026 12:15:24 +0200 Subject: [PATCH 40/67] fix typos and grammar and style issues --- .../discovery-pairing-authentication.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 4be6fee..d55f5d8 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -7,9 +7,9 @@ sidebar_position: 2 # Introduction -S2 Connect is a JSON based protocol specification implementing the EN50491-12-2 "S2" standard for home and building energy management. +S2 Connect is a JSON-based protocol specification implementing the EN50491-12-2 "S2" standard for home and building energy management. -This specification addresses everything needed to create a secure and interoperable implementation of the S2 standard. The communication layer concerns the discovery, the pairing, the application layer communication protocol, the authentication, the message data model and the serialization. It was specifically designed to support multiple deployment scenarios and to give a relative consistent user experience throughout these different scenarios. +This specification addresses everything needed to create a secure and interoperable implementation of the S2 standard. The communication layer concerns the discovery, the pairing, the application layer communication protocol, the authentication, the message data model and the serialization. It was specifically designed to support multiple deployment scenarios and to give a relatively consistent user experience throughout these different scenarios. The protocol is designed to specify communication between two devices, a resource (e.g. a heat pump or EV charger) and a (home) energy management system. It is worth noting that, while this specification focuses on describing the interaction between two components, a (home) energy management is likely to be communicating with multiple resources at the same time. @@ -47,12 +47,12 @@ This version of this specification is based on the following versions of the und S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other over IP networks. S2 Connect builds upon two other projects: * The **S2 Standard** (formally known as EN50491-12-2) describes the data models and interactions for S2 based communication -* The **S2 JSON** project formalizes the data models of the S2 standard into JSON Schemas for a messages based interaction between CEM and RM +* The **S2 JSON** project formalizes the data models of the S2 standard into JSON Schemas for a messages-based interaction between CEM and RM Both the S2 Standard and S2 JSON can be used independently from S2 Connect. ## Requirements -S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other. The CEM and RM are logical concepts within the S2 architecture, therefore the S2 standard does not make any assumptions on how and where the CEM and RM are deployed in a real life situation. In practice, the CEM could be deployed on a local gateway in a LAN or as a server somewhere on the internet (WAN), while the RM could be part of the device itself, deployed on an add-on module or on the internet as well. S2 Connect provides a single solution which can be used between devices connected through a LAN, on the internet, or a combination of those. +S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other. The CEM and RM are logical concepts within the S2 architecture, therefore, the S2 standard does not make any assumptions on how and where the CEM and RM are deployed in a real-life situation. In practice, the CEM could be deployed on a local gateway in a LAN or as a server somewhere on the internet (WAN), while the RM could be part of the device itself, deployed on an add-on module or on the internet as well. S2 Connect provides a single solution which can be used between devices connected through a LAN, on the internet, or a combination of those. S2 Connect implements the following high-level requirements: @@ -84,8 +84,8 @@ This specification uses the concepts that are defined below. | Term | Definition | | --- | --- | | Access token | A token that is used for setting up an S2 session. It is random binary data and must be generated by a cryptographically secure pseudorandom number generator and have a minimum length of 32 bytes. It is encoded using Base64. Also see [11A. Response status 200](#11a-response-status-200). | -| Communication client | The nodes which behaves as the HTTP client when initiating an S2 session. | -| Communication server | The nodes which behaves as the HTTP server when initiating an S2 session. | +| Communication client | The nodes that behaves as the HTTP client when initiating an S2 session. | +| Communication server | The nodes that behaves as the HTTP server when initiating an S2 session. | | End user | A person or entity that manages nodes. For the purpose of this specification it is assumed that there is already a trust relationship in place between this person and the nodes. This means that the way the trust relationship has been established is out of scope for this specification. | | End user environment | A restricted area within an application that contains all the nodes that belong to the end user. Also see [Deployment of nodes](#deployment-of-nodes). | | Endpoint | A service which can handle pairing requests or initiates pairing requests itself. An endpoint can represent one node, but could also represent many. | @@ -94,10 +94,10 @@ This specification uses the concepts that are defined below. | Node ID | A globally unique identifier for a node in the UUID format. | | Node ID alias | A short identifier for a node, which is unique in the context of a single endpoint. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Pairing attempt | The process of pairing two nodes. The process can be completed successfully or unsuccessfully. | -| Pairing client | The endpoint which behaves as the HTTP client when pairing with a node. | +| Pairing client | The endpoint that behaves as the HTTP client when pairing with a node. | | Pairing code | The pairing code is the string of characters the end user has to copy from the responder node user interface to the initiator node user interface, in order to pair the two nodes. The pairing code consists of a pairing token and if required a node ID alias. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Pairing endpoint registry | The central registry that keeps track of publicly available pairing servers. | -| Pairing server | The endpoint which behaves as the HTTP server when pairing with a node. | +| Pairing server | The endpoint that behaves as the HTTP server when pairing with a node. | | Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and a node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | | Session | A stateful exchange of S2 messages between two S2 nodes | @@ -225,7 +225,7 @@ If every node must be able to be the initiator node in certain situations, and t There are however two situations where this is not possible: -* **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that it is not possible to approach a LAN HTTP server from a WAN client. This specifications offers two approaches to this problem: +* **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that approaching a LAN HTTP server from a WAN client is not possible. This specifications offers two approaches to this problem: * Accept this limitation and not allow the WAN node to be the initiator node. Pairing can only be performed when the LAN node is the initiator node and the WAN node is the responder node. Special care must be taken to explain this to the end user. * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTP server in the cloud, even though the node itself is in the LAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients and must not be used by LAN clients. There must always be a method for purely LAN based pairing. * **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTP server is far more memory intensive than an HTTP client, there is an option to implement a LAN RM instance purely as an HTTP client. A long-polling mechanism is available to indicate to the HTTP Server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTP server. In other words: in this specific situation the initiator node behaves as the HTTP server, and the responder node only has to be an HTTP client. @@ -235,7 +235,7 @@ There are however two situations where this is not possible: # Formal specification and versioning (normative) -This document serves as an overall specification of the S2 Connect protocol. However, where possible, the protocol has been specified in a formal specification language in order to minimize possibilities for different interpretations and allow tooling to assist the implementation of the specification. Since many details are better described in these formal specification files, they are not described in this document. **Where the formal specification files and this document overlap, the formal specification file is leading.** +This document serves as an overall specification of the S2 Connect protocol. However, where possible, the protocol has been specified in a formal specification language in order to minimize possibilities for different interpretations and allow tooling to assist the implementation of the specification. Since many details are better described in these formal specification files, they are not described in this document. **Where the formal specification files and this document overlap, the formal specification file takes precedence.** | Part of specification | Description | Specification format | Location | | --- | --- | --- | --- | @@ -301,12 +301,12 @@ The client **must** perform the following checks during this request: If no checks fail the client **should** proceed to the next step. ### 2. Response status 200 -The server responds with a list of implement major versions of the pairing API. It is formatted as a JSON array contains all the supported version of the pairing API (e.g. `["v1"]`). +The server responds with a list of implemented major versions of the pairing API. It is formatted as a JSON array containing all the supported version of the pairing API (e.g. `["v1"]`). If the HTTP client does not support any of the provided versions, it means that the two endpoints are not compatible, and that further interaction is not possible. ### 3. Decide pairing version -From the provided list of major versions of the pairing API, the HTTP client **must** select one that is implement by the HTTP client itself (typically the highest supported version). The client **should** select the most recent version. +From the provided list of major versions of the pairing API, the HTTP client **must** select one that is implemented by the HTTP client itself (typically the highest supported version). The client **should** select the most recent version. ## HTTPS usage All HTTP-based interaction in S2 Connect is strictly based on HTTPS (i.e., HTTP over TLS). HTTP (without TLS) **may not** be used for S2 Connect, and it is **recommended** to not run an HTTP (without TLS) server. @@ -566,7 +566,7 @@ The client **must** send the following information in the request. For full norm | Property | Description | | --- | --- | | `clientNodeId` | The node ID of the node at the client that the end user intents to pair with the node at the server | -| `serverNodeId` | The node ID of the node at the server that the end user intents to pair wth the node at the client | +| `serverNodeId` | The node ID of the node at the server that the end user intents to pair with the node at the client | The server **should** respond with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). From fda8ff96dd0a81b8791d539a7df3bb825e404d31 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 28 May 2026 12:18:35 +0200 Subject: [PATCH 41/67] Consistent use of capitalization in HTTP server --- website/s2-connect/discovery-pairing-authentication.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index d55f5d8..af244bc 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -228,7 +228,7 @@ There are however two situations where this is not possible: * **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that approaching a LAN HTTP server from a WAN client is not possible. This specifications offers two approaches to this problem: * Accept this limitation and not allow the WAN node to be the initiator node. Pairing can only be performed when the LAN node is the initiator node and the WAN node is the responder node. Special care must be taken to explain this to the end user. * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTP server in the cloud, even though the node itself is in the LAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients and must not be used by LAN clients. There must always be a method for purely LAN based pairing. -* **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTP server is far more memory intensive than an HTTP client, there is an option to implement a LAN RM instance purely as an HTTP client. A long-polling mechanism is available to indicate to the HTTP Server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTP server. In other words: in this specific situation the initiator node behaves as the HTTP server, and the responder node only has to be an HTTP client. +* **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTP server is far more memory intensive than an HTTP client, there is an option to implement a LAN RM instance purely as an HTTP client. A long-polling mechanism is available to indicate to the HTTP server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTP server. In other words: in this specific situation the initiator node behaves as the HTTP server, and the responder node only has to be an HTTP client. ![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) @@ -466,7 +466,7 @@ The pairing code allows us to transfer two pieces of information by only botheri This protocol uses a two-way challenge response process to verify that both nodes have the same pairing token. For this process it doesn't matter which node has issued the pairing token and which node has the pairing token that was entered by the end user. The reason a two-way challenge response process is used to verify the pairing token is that it allows to establish trust without having to expose the pairing token. Both a challenge and a response are binary data, which are encoded using Base64. -The challenge that is generated by the HTTP Client is called the `clientHmacChallenge`. The response to this challenge, generated by the HTTP Server, is called the `clientHmacChallengeResponse`. The challenge that is generated by the HTTP Server is called the `serverHmacChallenge`. The response to this challenge, generated by the HTTP Client, is called the `serverHmacChallengeResponse`. +The challenge that is generated by the HTTP Client is called the `clientHmacChallenge`. The response to this challenge, generated by the HTTP server, is called the `clientHmacChallengeResponse`. The challenge that is generated by the HTTP server is called the `serverHmacChallenge`. The response to this challenge, generated by the HTTP Client, is called the `serverHmacChallengeResponse`. A challenge is a nonce; a random binary data. It **must** be generated by a cryptographically secure pseudorandom number generator and it **must** have a minimal length of 32 bytes. The response is calculated based on the function described below. Both the generator of the challenge and the receiver of the challenge calculate the response based several input parameters. Since both nodes should have the same input, both nodes should calculate the same response. The node that received the challenge sends it back to the node that generated the challenge. Now the node that generated the challenge simply has to check if the received response is identical to the expected response that he calculated himself. @@ -808,7 +808,7 @@ The client **must** perform the following checks during this request: If no checks fail the client **should** proceed to the next step. -### 7A. HTTP Server checks serverHmacChallengeResponse +### 7A. HTTP server checks serverHmacChallengeResponse The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). @@ -858,7 +858,7 @@ The server **must** perform the following checks during this request: If no checks fail the server **should** proceed to the next step. -### 7B. HTTP Server checks serverHmacChallengeResponse +### 7B. HTTP server checks serverHmacChallengeResponse The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. If the result is identical, the server **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). From 839aa6d17b8fe3184b8e46b9e32607a70eca90e1 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 28 May 2026 12:21:32 +0200 Subject: [PATCH 42/67] Update website/s2-connect/discovery-pairing-authentication.md --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index af244bc..9084734 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -330,7 +330,7 @@ For each pairing attempt, one endpoint must be the HTTP server, while the other > A LAN deployed RM implementation can choose if it implements the HTTP server, or that it implements the HTTP client and uses long-polling. This feature exists to accommodate RM implementations with constrained hardware. -A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. a node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). +A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. A node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). ## Discovery From 162c269b5dc96b4ef97c350d1a75a199ea5c35ee Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Mon, 1 Jun 2026 11:46:25 +0200 Subject: [PATCH 43/67] Update website/s2-connect/discovery-pairing-authentication.md --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 9084734..7bad794 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -169,7 +169,7 @@ iui->e: Pairing result (success or failure) Within this protocol we make a clear distinction between two types of identities: the one of the *endpoint* and the one of the *node* itself. -a node is an instance of a CEM or a RM. It is either a specific energy management system or service, or the representative of a physical energy flexible device, such as a heat pump or a home battery. It is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. +A node is an instance of a CEM or a RM. It is either a specific energy management system or service, or the representative of a physical energy flexible device, such as a heat pump or a home battery. It is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. The endpoint is basically the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. From 4328349afe06f7f821d62c268fc8a88b175b448b Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Mon, 1 Jun 2026 11:50:08 +0200 Subject: [PATCH 44/67] Removed left-over part about using DNS-SD for advertising WAN endpoints --- website/s2-connect/discovery-pairing-authentication.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 785529b..7b88a81 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -436,8 +436,6 @@ S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it An endpoint deployed in the LAN **should** publish it service through DNS-SD once it is ready for pairing, and until it shuts down. -A device has a presence in the LAN (e.g. a heat pump which is connected to the internet via the end user's WiFi), but has its RM deployed in the WAN, can still use DNS-SD to advertise its endpoint to any local nodes (e.g. a physical energy management device containing an CEM). In this case the device doesn't have to offer any actual service in the LAN; it merely uses the TXT record to advertise its WAN endpoint URL. An arbitrary port may be specified. - Two DNS-SD subtypes are used for endpoints. Subtypes can be used to filter services. * `_cem` is used when the endpoint contains one or more CEM node From f5deb75741558586e57f9ea1fa2511cedd20d503 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Mon, 1 Jun 2026 11:57:42 +0200 Subject: [PATCH 45/67] Update website/s2-connect/discovery-pairing-authentication.md Co-authored-by: Jorrit Nutma --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 26bdc2e..ab58f1b 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -296,7 +296,7 @@ The client **must** perform the following checks during this request: | --- | --- | | [Check TLS certificate](#certificate-validation) (with exemption of authenticity) | Pairing is failed, do not proceed with the pairing attempt | -The fingerprint of TLS certificate must be stored for later check. +If the root certificate is self-signed and there is no pinned certificate for this domain name, the fingerprint of TLS certificate must be stored for later check. If no checks fail the client **should** proceed to the next step. From 14e8b98c52604fa462c9fd1be1673305a49ccbb1 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Mon, 1 Jun 2026 15:27:16 +0200 Subject: [PATCH 46/67] Removed redundant pinned check, explaned a bit better what a pinned certificate is --- .../discovery-pairing-authentication.md | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index ab58f1b..639428d 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -777,8 +777,7 @@ The HTTP client checks the `clientHmacChallengeResponse` provided by the HTTP se If the result is identical, the client **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt. It **must** attempt to inform the HTTP server of this by doing an HTTP request to `finalizePairing` where the value of `success` must be `false`. -Note that in case of a local server, the TLS certificate fingerprint is part of the challenge. So if the challenge succeeds, the certificate fingerprint is correct, and the certificate can be trusted. The client **must** pin this certificate, and trust this certificate for future use. - +Note that in case of a local server, the TLS certificate fingerprint is part of the challenge. So if the challenge succeeds, the certificate fingerprint is correct, and the certificate can be trusted. The client **must** pin the self-signed CA (root) certificate, and trust this certificate for the remainder of the pairing relation. ### 5. Calculate serverHmacChallengeResponse The HTTP client calculates a response to the provided `serverHmacChallenge` using the hashing algorithm as indicated in the `selectedHmacHashingAlgorithm`. For details see [Challenge response process](#challenge-response-process). @@ -801,7 +800,6 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | If no checks fail the client **should** proceed to the next step. @@ -828,7 +826,7 @@ If the response is understood and properly formatted, the HTTP client **should** The HTTP sends the connection details to the HTTP server. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 5. -In this case the pairing server will become the communication client. Once the pairing server becomes the communication client, it does not know what the certificate that the communication server will use. That is why it needs to provide it using the property `certificateFingerprint`. This property is a map, where the key of the map is the hashing algorithm used to generate the fingerprint, and the value is the fingerprint itself. The hashing function `SHA256` and the related fingerprint **must** always be provided. +In this case the pairing server will become the communication client. Once the pairing server becomes the communication client, it does not know what the certificate that the communication server will use. That is why it needs to provide the fingerprint of its CA (root) certificate using the property `certificateFingerprint`. This property is a map, where the key of the map is the hashing algorithm used to generate the fingerprint, and the value is the fingerprint itself. The hashing function `SHA256` and the related fingerprint **must** always be provided. The communication client **must** pin this certificate to the domain name of the communication server. | Information | Description | | --- | --- | @@ -842,7 +840,6 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | If no checks fail the client **should** proceed to the next step. @@ -873,8 +870,6 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | - If no checks fail the client **should** proceed to the next step. @@ -970,7 +965,6 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Initiation is failed, do not proceed with the initiation attempt | -| If self-signed TLS certificate, check if certificate is pinned | Initiation is failed, do not proceed with the initiation attempt | If no checks fail the client **should** proceed to the next step. @@ -1037,8 +1031,6 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Do not proceed with session, try again later | -| If self-signed TLS certificate, check if certificate is pinned | Do not proceed with session, try again later | - If no checks fail the client **should** proceed. @@ -1093,7 +1085,6 @@ The client **must** perform the following checks during this request: | Check | How to proceed if check fails | | --- | --- | | [Check TLS certificate](#certificate-validation) | Websocket connection failed, do not proceed with the connection attempt | -| If self-signed TLS certificate, check if certificate is pinned | Websocket connection failed, do not proceed with the connection attempt | If no checks fail the client **should** proceed to the next step. @@ -1230,20 +1221,20 @@ LAN deployed nodes will have a self-signed root certificate, and a server (leaf) The `endpoint`, `nodes`, `preparePairing` and `cancelPreparePairing` operations can be called before the pairing has happened. For these operations the client **must** accept the self-signed certificates, even though it cannot trust the root certificate. -Also when attempting pairing (the `requestPairing` operation) the client **must** accept the self-signed certificate. During the pairing process trust is established through a two-sided challenge response mechanism. If the two-sided challenge response succeeds, that means that the client can now trust the server of the node it is paired with. The client **must** store the self-signed root certificate used by the server. Alternatively, when the pairing server becomes the communication client, the pairing client will send the fingerprint of the self-signed root certificate that the communication server will use (see [6B. POST /[version]/postConnectionDetails](#6b-post-versionpostconnectiondetails)). +Also when attempting pairing (the `requestPairing` operation) the client **must** accept the self-signed certificate. During the pairing process trust is established through a two-sided challenge response mechanism. If the two-sided challenge response succeeds, that means that the client can now trust the server of the node it is paired with. The client **must** pin the CA (root) certificate to the domain name. This means that it must check for every furter interaction (for the duration of the pairing relation with this server) if it still uses the same CA (root) certificate. Alternatively, when the pairing server becomes the communication client, the pairing client will send the fingerprint of the self-signed CA (root) certificate that the communication server will use (see [6B. POST /[version]/postConnectionDetails](#6b-post-versionpostconnectiondetails)). -When performing session initiation and unpairing the communication client **must** validate that server certificate and check that the certificate was signed by the self-signed root certificate that was stored in the previous step. +When performing session initiation and unpairing the communication client **must** validate that server certificate and check that the certificate was signed by the self-signed root certificate that was pinned in the previous step. ### Updating the certificates A server can update its leaf certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. -If the server is deployed in the LAN, and thus uses a self-signed root certificate, the root certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server (or the device that hosts the server). If the used crypto for the the CA certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its leaf certificate at least once every 6 months. +If the server is deployed in the LAN, and thus uses a self-signed root certificate, the root certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server (or the device that hosts the server). If the used crypto for the the CA (root) certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its leaf certificate at least once every 6 months. ### Certificate validation Certificate checks are mentioned several times in this specification. The check consists of these parts: - Authenticity: In case of a WAN server, is the certificate issued by a trusted CA (using the chain of trust)? For a LAN server, is the root certificate pinned to the server’s domain name? - Domain name validation: has the certificate been issued for the (local) domain name of the server? -- Expiration data: has the certificate not been expired? +- Expiration date: has the certificate not been expired? - Integrity: Has the certificate not been tampered with? This is verified by checking whether the signature is valid. - Cryptography check: has an allowed crypto algorithm been used? See the section on [Cipher suites](#cipher-suites). From a8c5e6e871da94ca7092f971619c38133eeb5b2f Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Mon, 1 Jun 2026 15:47:50 +0200 Subject: [PATCH 47/67] Used HTTPS consistently instead of HTTP --- .../discovery-pairing-authentication.md | 187 +++++++++--------- .../api_version_selection.png | Bin 5752 -> 5891 bytes .../pairing_http_process.png | Bin 28944 -> 29541 bytes .../session-initiation.png | Bin 11416 -> 11500 bytes 4 files changed, 94 insertions(+), 93 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 426843a..996def5 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -32,6 +32,7 @@ This version of this specification is based on the following versions of the und | DNS | Domain Name System | | DNS-SD | DNS Service Discovery | | HTTP | HyperText Transfer Protocol | +| HTTPS | HyperText Transfer Protocol Secure | | LAN | Local Area Network (i.e. a local network, typically constrained to the building) | | mDNS | Multicast DNS | | NAT | Network Address Translation | @@ -84,8 +85,8 @@ This specification uses the concepts that are defined below. | Term | Definition | | --- | --- | | Access token | A token that is used for setting up an S2 session. It is random binary data and must be generated by a cryptographically secure pseudorandom number generator and have a minimum length of 32 bytes. It is encoded using Base64. Also see [11A. Response status 200](#11a-response-status-200). | -| Communication client | The nodes that behaves as the HTTP client when initiating an S2 session. | -| Communication server | The nodes that behaves as the HTTP server when initiating an S2 session. | +| Communication client | The nodes that behaves as the HTTPS client when initiating an S2 session. | +| Communication server | The nodes that behaves as the HTTPS server when initiating an S2 session. | | End user | A person or entity that manages nodes. For the purpose of this specification it is assumed that there is already a trust relationship in place between this person and the nodes. This means that the way the trust relationship has been established is out of scope for this specification. | | End user environment | A restricted area within an application that contains all the nodes that belong to the end user. Also see [Deployment of nodes](#deployment-of-nodes). | | Endpoint | A service which can handle pairing requests or initiates pairing requests itself. An endpoint can represent one node, but could also represent many. | @@ -94,10 +95,10 @@ This specification uses the concepts that are defined below. | Node ID | A globally unique identifier for a node in the UUID format. | | Node ID alias | A short identifier for a node, which is unique in the context of a single endpoint. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Pairing attempt | The process of pairing two nodes. The process can be completed successfully or unsuccessfully. | -| Pairing client | The endpoint that behaves as the HTTP client when pairing with a node. | +| Pairing client | The endpoint that behaves as the HTTPS client when pairing with a node. | | Pairing code | The pairing code is the string of characters the end user has to copy from the responder node user interface to the initiator node user interface, in order to pair the two nodes. The pairing code consists of a pairing token and if required a node ID alias. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Pairing endpoint registry | The central registry that keeps track of publicly available pairing servers. | -| Pairing server | The endpoint that behaves as the HTTP server when pairing with a node. | +| Pairing server | The endpoint that behaves as the HTTPS server when pairing with a node. | | Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and a node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | | Session | A stateful exchange of S2 messages between two S2 nodes | @@ -177,7 +178,7 @@ The endpoint is basically the application that hosts the nodes. In a LAN deploym An S2 connection basically consists of four steps: discovery, pairing, communication and unpairing. For these steps different types of technology are used. -One of the main technologies the process relies on is HTTP REST. All interactions based on HTTP are formally described in OpenAPI specification files. [OpenAPI](https://swagger.io/specification/) is a formal language for specifying HTTP based API's. It can be used to generate reference documentation for developers, as well as stub code for many programming languages. +One of the main technologies the process relies on is HTTPS REST. All interactions based on HTTPS are formally described in OpenAPI specification files. [OpenAPI](https://swagger.io/specification/) is a formal language for specifying HTTP(S) based API's. It can be used to generate reference documentation for developers, as well as stub code for many programming languages. ### Discovery @@ -188,9 +189,9 @@ The first step is finding the responder node from the initiator node. In princip ### Pairing -The pairing process itself is completely based on HTTP REST. One node behaves as the HTTP server, and the other as the HTTP client. This process is described in an OpenAPI file. The process consists of multiple steps. If the pairing process is completed successfully, the nodes will agree on an access token. This token is used to initiate communication or to unpair. +The pairing process itself is completely based on HTTPS REST. One node behaves as the HTTPS server, and the other as the HTTPS client. This process is described in an OpenAPI file. The process consists of multiple steps. If the pairing process is completed successfully, the nodes will agree on an access token. This token is used to initiate communication or to unpair. -We'll refer to the endpoint that behaves as the HTTP server during the pairing process as the *pairing server*, and the client as the *pairing client*. +We'll refer to the endpoint that behaves as the HTTPS server during the pairing process as the *pairing server*, and the client as the *pairing client*. Pairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) @@ -198,37 +199,37 @@ Pairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployment Communication is setting up the actual session, where S2 messages are being exchanged. -The process always starts with HTTP based communication, but then is handed over to a protocol which supports a two-way messages based communication channel. Currently the only protocol that is being used is WebSockets, but there are plans to add other options in the future. The HTTP interface is also specified in an OpenAPI file, together with the unpairing process. +The process always starts with HTTPS based communication, but then is handed over to a protocol which supports a two-way messages based communication channel. Currently the only protocol that is being used is WebSockets, but there are plans to add other options in the future. The HTTPS interface is also specified in an OpenAPI file, together with the unpairing process. -We'll refer to the endpoint that behaves as the HTTP server during the communication process as the *communication server*, and the client as the *communication client*. +We'll refer to the endpoint that behaves as the HTTPS server during the communication process as the *communication server*, and the client as the *communication client*. -It should be noted that pairing and communication are two separate HTTP interfaces, that don't have to be used in the same way. It could be that a node is an pairing client, but then becomes a communication server. This depends on the deployment of the nodes (see [Pairing details for different deployments](#pairing-details-for-different-deployments)). +It should be noted that pairing and communication are two separate HTTPS interfaces, that don't have to be used in the same way. It could be that a node is an pairing client, but then becomes a communication server. This depends on the deployment of the nodes (see [Pairing details for different deployments](#pairing-details-for-different-deployments)). Communication interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) -After the HTTP interaction a WebSocket is established (other transport protocols will be added in the future). The communication server is always the WebSocket server. This server must use the same TLS certificate as the HTTP server. +After the HTTPS interaction a WebSocket is established (other transport protocols will be added in the future). The communication server is always the WebSocket server. This server must use the same TLS certificate as the HTTPS server. ### Unpairing -Either node can take the initiative to unpair from the other node. This is done using the same HTTP OpenAPI specification and the same HTTP server and client as the communication. The details for unpairing differ depending if it is the communication server or if it is the communication client that initiates the unpairing process. +Either node can take the initiative to unpair from the other node. This is done using the same HTTPS OpenAPI specification and the same HTTPS server and client as the communication. The details for unpairing differ depending if it is the communication server or if it is the communication client that initiates the unpairing process. Unpairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) ## Pairing details for different deployments -As explained, the pairing process is based on HTTP REST calls. That means that for every pairing attempt, one node behaves as the HTTP server, and one HTTP node behaves as the pairing client. The logical solution would be to make the initiator node the HTTP client and the responder node the HTTP server. After all, it is the HTTP client that takes the initiative to contact the HTTP server. The HTTP server cannot take the initiative to contact the HTTP client. +As explained, the pairing process is based on HTTPS REST calls. That means that for every pairing attempt, one node behaves as the HTTPS server, and one node behaves as the HTTPS client. The logical solution would be to make the initiator node the HTTPS client and the responder node the HTTPS server. After all, it is the HTTPS client that takes the initiative to contact the HTTPS server. The HTTPS server cannot take the initiative to contact the HTTPS client. The objective is to have all nodes be able to be the initiator node, as well as the responder node. This is necessary to provide a consistent user experience. The end user might not be aware which node is deployed in the LAN or in the WAN, and then it might be confusing that, for example, his energy management system both provides pairing codes and asks for pairing codes. -If every node must be able to be the initiator node in certain situations, and the responder node in other situations, and the easiest solution is to implement the initiator as HTTP client and the responder as HTTP server, you might come to the conclusion that every node needs to be able to behave both as an HTTP server and as an HTTP client. +If every node must be able to be the initiator node in certain situations, and the responder node in other situations, and the easiest solution is to implement the initiator as HTTPS client and the responder as HTTPS server, you might come to the conclusion that every node needs to be able to behave both as an HTTPS server and as an HTTPS client. There are however two situations where this is not possible: -* **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that approaching a LAN HTTP server from a WAN client is not possible. This specifications offers two approaches to this problem: +* **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that approaching a LAN HTTPS server from a WAN client is not possible. This specifications offers two approaches to this problem: * Accept this limitation and not allow the WAN node to be the initiator node. Pairing can only be performed when the LAN node is the initiator node and the WAN node is the responder node. Special care must be taken to explain this to the end user. - * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTP server in the cloud, even though the node itself is in the LAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients and must not be used by LAN clients. There must always be a method for purely LAN based pairing. -* **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTP server is far more memory intensive than an HTTP client, there is an option to implement a LAN RM instance purely as an HTTP client. A long-polling mechanism is available to indicate to the HTTP server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTP server. In other words: in this specific situation the initiator node behaves as the HTTP server, and the responder node only has to be an HTTP client. + * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTPS server in the cloud, even though the node itself is in the LAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients and must not be used by LAN clients. There must always be a method for purely LAN based pairing. +* **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTPS server is far more memory intensive than an HTTPS client, there is an option to implement a LAN RM instance purely as an HTTPS client. A long-polling mechanism is available to indicate to the HTTPS server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTPS server. In other words: in this specific situation the initiator node behaves as the HTTPS server, and the responder node only has to be an HTTPS client. ![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) @@ -239,9 +240,9 @@ This document serves as an overall specification of the S2 Connect protocol. How | Part of specification | Description | Specification format | Location | | --- | --- | --- | --- | -| S2 Connect pairing API | HTTP based interaction to pair two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-pairing.yml) | -| S2 Connect connection API | HTTP based interaction set up a communication channel for S2 messages between two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-connection-init.yml) | -| S2 Connect WAN pairing endpoint registry API | HTTP based interface to query the registry | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-wan-endpoint-registry.yml) | +| S2 Connect pairing API | HTTPS based interaction to pair two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-pairing.yml) | +| S2 Connect connection API | HTTPS based interaction set up a communication channel for S2 messages between two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-connection-init.yml) | +| S2 Connect WAN pairing endpoint registry API | HTTPS based interface to query the registry | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-wan-endpoint-registry.yml) | | S2 JSON message structure | The types of S2 messages that can be exchanges between nodes | JSON schema files | [Github](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema) | ## Versioning of OpenAPI files @@ -251,7 +252,7 @@ The minor version is increased when backwards compatible changes are made. Be aw The major version is increased when non-backwards compatible changes are made. -The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTP server and HTTP clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must server all version on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as port of the URL (e.g. `["v1", "v2"]`). +The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTPS server and HTTPS clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must server all version on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as port of the URL (e.g. `["v1", "v2"]`). ## Versioning of JSON Schema files JSON Schema uses its own versioning scheme, which is based on an `major.minor.patch` scheme. When negotiating the S2 JSON version number, the exact version string **must** be used (e.g. `v1.0.0`). @@ -275,8 +276,8 @@ The image below depicts the interactions between client and server for the proce ``` @startuml -participant "HTTP Client" as Client -participant "HTTP Server" as Server +participant "HTTPS Client" as Client +participant "HTTPS Server" as Server Client->Server++: 1. GET / (index containing pairing API versions) Server-->Client: 2. Response status 200 @@ -288,7 +289,7 @@ Client->Client: 3. Decide pairing version
### 1. GET / (index containing pairing API versions) -Since the HTTP client does not know which major versions of the pairing API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). +Since the HTTPS client does not know which major versions of the pairing API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). The client **must** perform the following checks during this request: @@ -303,23 +304,23 @@ If no checks fail the client **should** proceed to the next step. ### 2. Response status 200 The server responds with a list of implemented major versions of the pairing API. It is formatted as a JSON array containing all the supported version of the pairing API (e.g. `["v1"]`). -If the HTTP client does not support any of the provided versions, it means that the two endpoints are not compatible, and that further interaction is not possible. +If the HTTPS client does not support any of the provided versions, it means that the two endpoints are not compatible, and that further interaction is not possible. ### 3. Decide pairing version -From the provided list of major versions of the pairing API, the HTTP client **must** select one that is implemented by the HTTP client itself (typically the highest supported version). The client **should** select the most recent version. +From the provided list of major versions of the pairing API, the HTTPS client **must** select one that is implemented by the HTTPS client itself (typically the highest supported version). The client **should** select the most recent version. ## HTTPS usage All HTTP-based interaction in S2 Connect is strictly based on HTTPS (i.e., HTTP over TLS). HTTP (without TLS) **may not** be used for S2 Connect, and it is **recommended** to not run an HTTP (without TLS) server. -HTTP clients and servers **must** support HTTP version 1.1. It is **recommended** to support more recent version of HTTP as well. +HTTPS clients and servers **must** support HTTP version 1.1. It is **recommended** to support more recent version of HTTP as well. # Pairing process (normative) The pairing process is based on the trust relation that the end user has with both the CEM and the RM instances. That trust relation is out of scope for this specification and is up to CEM and RM providers to implement. -For each pairing attempt, one endpoint must be the HTTP server, while the other is the HTTP client. The table below defines which endpoint is het HTTP server or client is which situation. +For each pairing attempt, one endpoint must be the HTTPS server, while the other is the HTTPS client. The table below defines which endpoint is het HTTPS server or client is which situation. -| Initiator node | Responder node | Responder is exclusively RM? | Initiator HTTP role | Responder HTTP role | WAN pairing server | Remark | +| Initiator node | Responder node | Responder is exclusively RM? | Initiator HTTPS role | Responder HTTPS role | WAN pairing server | Remark | | ----------------- | ----------------- | ---------------------------- | ------------------- | ------------------- | --------------------- | ------ | | WAN | WAN | Doesn't matter | Client | Server | n/a | | | WAN | LAN | Doesn't matter | Client | n/a | Server | The WAN pairing server for LAN deployment is optional | @@ -328,7 +329,7 @@ For each pairing attempt, one endpoint must be the HTTP server, while the other | LAN | LAN | Yes | Client | Server | n/a | Alternative for long-polling. Decision to implement the server or the client is up to the RM endpoint. | | LAN | LAN | No | Client | Server | n/a | | -> A LAN deployed RM implementation can choose if it implements the HTTP server, or that it implements the HTTP client and uses long-polling. This feature exists to accommodate RM implementations with constrained hardware. +> A LAN deployed RM implementation can choose if it implements the HTTPS server, or that it implements the HTTPS client and uses long-polling. This feature exists to accommodate RM implementations with constrained hardware. A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. A node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). @@ -377,7 +378,7 @@ DNS-SD is used to automatically discover nodes from a node that is deployed in t * To discover another node that is deployed in the LAN, which is the responder node * To advertise a [long polling URL](#long-polling) so other initiator nodes in the LAN could connect to this node -S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it is an HTTP based protocol. S2 Connect uses the following DNS-SD values: +S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it is an HTTPS based protocol. S2 Connect uses the following DNS-SD values: | DNS-SD property | Value for S2 | | --- | --- | @@ -462,13 +463,13 @@ The pairing code allows us to transfer two pieces of information by only botheri This protocol uses a two-way challenge response process to verify that both nodes have the same pairing token. For this process it doesn't matter which node has issued the pairing token and which node has the pairing token that was entered by the end user. The reason a two-way challenge response process is used to verify the pairing token is that it allows to establish trust without having to expose the pairing token. Both a challenge and a response are binary data, which are encoded using Base64. -The challenge that is generated by the HTTP Client is called the `clientHmacChallenge`. The response to this challenge, generated by the HTTP server, is called the `clientHmacChallengeResponse`. The challenge that is generated by the HTTP server is called the `serverHmacChallenge`. The response to this challenge, generated by the HTTP Client, is called the `serverHmacChallengeResponse`. +The challenge that is generated by the HTTPS Client is called the `clientHmacChallenge`. The response to this challenge, generated by the HTTPS server, is called the `clientHmacChallengeResponse`. The challenge that is generated by the HTTPS server is called the `serverHmacChallenge`. The response to this challenge, generated by the HTTPS client, is called the `serverHmacChallengeResponse`. A challenge is a nonce; a random binary data. It **must** be generated by a cryptographically secure pseudorandom number generator and it **must** have a minimal length of 32 bytes. The response is calculated based on the function described below. Both the generator of the challenge and the receiver of the challenge calculate the response based several input parameters. Since both nodes should have the same input, both nodes should calculate the same response. The node that received the challenge sends it back to the node that generated the challenge. Now the node that generated the challenge simply has to check if the received response is identical to the expected response that he calculated himself. The algorithm to calculate the response is based on the HMAC (hash-based message authentication code) function. This function has a *key* and a *message* as arguments. Most programming languages have a function or library available that provides HMAC functions. -The HMAC function itself uses a cryptographic hash function for its calculations. Since cryptographic hash functions might contain vulnerabilities, this protocol uses a simple selection mechanism for the cryptographic hash function. The HTTP client sends with the requestPairing HTTP request a list of supported hash functions. In the response the HTTP server indicates which hash function it has selected from this list. This function **must** be used for all response calculations during het pairing attempt. Currently there is only one hash function available (SHA256), but other options might be added in the future. +The HMAC function itself uses a cryptographic hash function for its calculations. Since cryptographic hash functions might contain vulnerabilities, this protocol uses a simple selection mechanism for the cryptographic hash function. The HTTPS client sends with the requestPairing HTTPS request a list of supported hash functions. In the response the HTTPS server indicates which hash function it has selected from this list. This function **must** be used for all response calculations during het pairing attempt. Currently there is only one hash function available (SHA256), but other options might be added in the future. It order to avoid man-in-the-middle attacks, information about the connection in also used as input for calculating the response. When both nodes are deployed in the LAN, the SHA256 fingerprint of the server certificate is used. In other scenarios, the domain name of the server is included in the calculation of the response. @@ -492,7 +493,7 @@ Where: | `C` | Binary data | Challenge | | `T` | Binary data | Pairing token | | `F` | Binary data | SHA256 fingerprint of the TLS server certificate (i.e. leaf certificate) | -| `D` | Binary data | The domain name of the HTTP server, including subdomains, without protocol or trailing slashes (e.g. `pairing.s2.example.com`) | +| `D` | Binary data | The domain name of the HTTPS server, including subdomains, without protocol or trailing slashes (e.g. `pairing.s2.example.com`) | | `\|\|` | Function | Concatenation function | ## Getting endpoint information @@ -570,16 +571,16 @@ The server **should** respond with HTTP status code 204 (even when it does not r > This section is only applicable for LAN-LAN pairing -The long-polling feature is intended to support endpoints that only want to implement an HTTP client, and not an HTTP server. Typically this is because the endpoint runs on constrained hardware. An endpoint is only allowed to only implement the client if it exclusively hosts nodes that have the RM role. +The long-polling feature is intended to support endpoints that only want to implement an HTTPS client, and not an HTTPS server. Typically this is because the endpoint runs on constrained hardware. An endpoint is only allowed to only implement the client if it exclusively hosts nodes that have the RM role. -Imagine we have endpoints, one only hosting a CEM node, and one only hosting a RM node. The RM runs on constrained hardware, and only implements the HTTP client. We have two situations: +Imagine we have endpoints, one only hosting a CEM node, and one only hosting a RM node. The RM runs on constrained hardware, and only implements the HTTPS client. We have two situations: -1. **The RM is the initiator node and the CEM is the responder node**: The CEM issues a pairing code, the end users enters the pairing code in the UI of the RM. The RM endpoint (HTTP client) then sends a normal HTTP request to the CEM endpoint (HTTP server) to initiate pairing. The normal pairing process can be used, and long-polling is not required. -2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTP server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTP client). +1. **The RM is the initiator node and the CEM is the responder node**: The CEM issues a pairing code, the end users enters the pairing code in the UI of the RM. The RM endpoint (HTTPS client) then sends a normal HTTPS request to the CEM endpoint (HTTPS server) to initiate pairing. The normal pairing process can be used, and long-polling is not required. +2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTPS server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTPS client). -For the second situation the long-polling feature can be used. It can be used by the initiator node (the HTTP server) to notify the responder node (the HTTP client) it wants to pair. +For the second situation the long-polling feature can be used. It can be used by the initiator node (the HTTPS server) to notify the responder node (the HTTPS client) it wants to pair. -> Informative: Long-polling is a technique that allows the server to send signals to the client without a significant delay, and without relying on additional technologies such as Websockets or Server-Sent Events. The common alternative is polling, where the client sends a request on a regular interval; let's say every 30 seconds. Polling creates a delay from the perspective of the server. If the server wants to send something to the client, it has to wait until the client contacts the server; which in the worst case 30 seconds. With long-polling the server doesn't immediately respond the the request (a hanging HTTP request). It responds immediately when the server wants the client to do something, or just before the request would time out. After receiving the response from the server the client immediately opens a new request to allow the server to send signals the client again. +> Informative: Long-polling is a technique that allows the server to send signals to the client without a significant delay, and without relying on additional technologies such as Websockets or Server-Sent Events. The common alternative is polling, where the client sends a request on a regular interval; let's say every 30 seconds. Polling creates a delay from the perspective of the server. If the server wants to send something to the client, it has to wait until the client contacts the server; which in the worst case 30 seconds. With long-polling the server doesn't immediately respond the the request (a hanging HTTPS request). It responds immediately when the server wants the client to do something, or just before the request would time out. After receiving the response from the server the client immediately opens a new request to allow the server to send signals the client again. The long-polling feature fulfills the following functionality: * Make the existence of the client known to server, together with the nodes IDs of the nodes that are represented by the client endpoint @@ -612,7 +613,7 @@ The table below indicates how the client should respond to the requests of the s | 401 | n/a | n/a | Stop long-polling, do not attempt long-polling with this node again | n/a | | 500 | n/a | n/a | Wait before trying to send the next request | Only the `clientNodeId`| -2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTP server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTP client). +2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTPS server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTPS client). When the server sends the `requestPairing` action, the node on the client must already have issued a pairing token. If the node uses a dynamic pairing code, it could be the case that the pairing code has expired, or that no pairing code has been issued in the first place. In that case the client **must** perform a new request with an `errorMessage` containing the value `NoValidTokenOnPairingClient` in the object associated with the node ID of the node should have attempted to pair. @@ -622,7 +623,7 @@ The activity diagram below summarizes the complete long-polling process from the ## Pairing interaction -The pairing process itself consists of several HTTP interactions between client and server. The image below depicts a successful pairing process between two nodes. +The pairing process itself consists of several HTTPS interactions between client and server. The image below depicts a successful pairing process between two nodes. ![image](@site/static/img/communication-layer/pairing_http_process.png) @@ -631,8 +632,8 @@ The pairing process itself consists of several HTTP interactions between client ``` @startuml -participant "HTTP Client" as Client -participant "HTTP Server" as Server +participant "HTTPS Client" as Client +participant "HTTPS Server" as Server 'compatibility check Client->Server: 1. POST /[version]/requestPairing @@ -641,24 +642,24 @@ Server->Server: 2. Calculate clientHmacChallengeResponse Server-->Client: 3. Response status 200 deactivate Server -Client->Client: 4. HTTP Client checks clientHmacChallengeResponse +Client->Client: 4. HTTPS Client checks clientHmacChallengeResponse -Note over Client: HTTP Client now trusts HTTP Server +Note over Client: HTTPS Client now trusts HTTPS Server Client->Client: 5. Calculate serverHmacChallengeResponse alt Pairing server is Communication Server Client->Server: 6A. POST /[version]/requestConnectionDetails activate Server - Server->Server: 7A. HTTP Server checks serverHmacChallengeResponse - Note over Server: HTTP Server now trusts HTTP Client + Server->Server: 7A. HTTPS Server checks serverHmacChallengeResponse + Note over Server: HTTPS Server now trusts HTTPS Client Server-->Client: 8A. Response status 200 deactivate Server else Pairing server is Communication Client Client->Server: 6B. POST /[version]/postConnectionDetails activate Server - Server->Server: 7B. HTTP Server checks serverHmacChallengeResponse - Note over Server: HTTP Server now trusts HTTP Client + Server->Server: 7B. HTTPS Server checks serverHmacChallengeResponse + Note over Server: HTTPS Server now trusts HTTPS Client Server-->Client: 8B. Response status 204 deactivate Server end @@ -677,14 +678,14 @@ Note over Client, Server: Pairing finalized Before two node can be paired, the following preconditions must be met. -1. The HTTP server and the HTTP client can only start with a pairing request when they are fully initialized and have all the details of the nodes it represents available. -2. The HTTP client must have the base URL of the pairing API (e.g. `https://hostname.local/pairing/`) -3. The HTTP client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) +1. The HTTPS server and the HTTPS client can only start with a pairing request when they are fully initialized and have all the details of the nodes it represents available. +2. The HTTPS client must have the base URL of the pairing API (e.g. `https://hostname.local/pairing/`) +3. The HTTPS client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) 4. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. -> Note: The initiator node could be the HTTP server or the HTTP client +> Note: The initiator node could be the HTTPS server or the HTTPS client -If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the pairing process. +If the HTTPS client does not fulfill these preconditions, it **cannot** send the first HTTPS request of the pairing process. ### 1. POST /[version]/requestPairing In the first POST request the client provides the server with same information about itself. The main purpose of this is to check if these two nodes are compatible. @@ -727,8 +728,8 @@ The server **must** perform the checks in the table below to make sure that it c | Does the server accept any of the provided hashing algorithms for the challenge response process? | `IncompatibleHmacHashingAlgorithms` | No | | Is there overlap between the communication protocols? | `IncompatibleCommunicationProtocols` | Yes | | Is there overlap between the S2 message versions? | `IncompatibleS2MessageVersions` | Yes | -| If the targeted node on the HTTP server is the initiator node, did the end user provide a valid pairing token? | `NoValidPairingTokenOnPairingServer` | No | -| If the targeted node on the HTTP server is the responder node, does the node have a pairing token which has not expired? | `NoValidPairingTokenOnPairingServer` | No | +| If the targeted node on the HTTPS server is the initiator node, did the end user provide a valid pairing token? | `NoValidPairingTokenOnPairingServer` | No | +| If the targeted node on the HTTPS server is the responder node, does the node have a pairing token which has not expired? | `NoValidPairingTokenOnPairingServer` | No | | Is this is a WAN pairing server for a LAN endpoint, does the client have a WAN deployment? | `Other` | No | > Note: If the node that is being paired is an RM which is already paired, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation must be unpaired. @@ -745,7 +746,7 @@ The server selects an hashing algorithm for the challenge response function from To mitigate brute-force attacks, the server **must** enforce a mandatory delay of one second before sending its response to the client (step 3). For any given node at the server, pairing attempts **must** be handled sequentially, such that each second only one pairing attempt can be processed for a node. Pairing attempts targeting different nodes **may** be processed in parallel. This way, a server representing multiple nodes is not globally limited to one pairing attempt per second, but instead enforces the one-second rate limit independently per node. ### 3. Response status 200 -In order to formulate a response, the server **must** generate a `pairingAttemptId`. This is an identifier that **must** be generated by a cryptographically secure pseudorandom number generator and encoded using Base64. This identifier is used to keep track of all the HTTP interactions during the pairing attempt, and **must** be provided by the HTTP client as a header with all subsequent interactions. A pairing attempt **must** be completed within 15 seconds, or else the server **must** assume the pairing attempt has failed. +In order to formulate a response, the server **must** generate a `pairingAttemptId`. This is an identifier that **must** be generated by a cryptographically secure pseudorandom number generator and encoded using Base64. This identifier is used to keep track of all the HTTPS interactions during the pairing attempt, and **must** be provided by the HTTPS client as a header with all subsequent interactions. A pairing attempt **must** be completed within 15 seconds, or else the server **must** assume the pairing attempt has failed. The server responds with the following information (for full details see the OpenAPI specification file): @@ -755,8 +756,8 @@ The server responds with the following information (for full details see the Ope | `serverNodeDescription` | Information about the node that is being targeted, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the responder node | | `serverEndpointDescription` | Information about the server endpoint. An important field is the deployment. | | `selectedHmacHashingAlgorithm` | The hashing algorithm for the challenge response function as selected in step 2 | -| `clientHmacChallengeResponse` | The response to the challenge provided by the HTTP client as calculated in step 2 | -| `serverHmacChallenge` | The challenge created by the HTTP server for the challenge response process (see [Challenge response process](#challenge-response-process).) | +| `clientHmacChallengeResponse` | The response to the challenge provided by the HTTPS client as calculated in step 2 | +| `serverHmacChallenge` | The challenge created by the HTTPS server for the challenge response process (see [Challenge response process](#challenge-response-process).) | The client **must** perform the following checks of this data. @@ -768,24 +769,24 @@ The client **must** perform the following checks of this data. If no checks fail the server **should** proceed to the next step. -### 4. HTTP Client checks clientHmacChallengeResponse -The HTTP client checks the `clientHmacChallengeResponse` provided by the HTTP server in step 3. It does that by calculating the response itself, and checking if the results is identical to the `clientHmacChallengeResponse`. +### 4. HTTPS Client checks clientHmacChallengeResponse +The HTTPS client checks the `clientHmacChallengeResponse` provided by the HTTPS server in step 3. It does that by calculating the response itself, and checking if the results is identical to the `clientHmacChallengeResponse`. -If the result is identical, the client **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt. It **must** attempt to inform the HTTP server of this by doing an HTTP request to `finalizePairing` where the value of `success` must be `false`. +If the result is identical, the client **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt. It **must** attempt to inform the HTTPS server of this by doing an HTTPS request to `finalizePairing` where the value of `success` must be `false`. Note that in case of a local server, the TLS certificate fingerprint is part of the challenge. So if the challenge succeeds, the certificate fingerprint is correct, and the certificate can be trusted. The client **must** pin the self-signed CA (root) certificate, and trust this certificate for the remainder of the pairing relation. ### 5. Calculate serverHmacChallengeResponse -The HTTP client calculates a response to the provided `serverHmacChallenge` using the hashing algorithm as indicated in the `selectedHmacHashingAlgorithm`. For details see [Challenge response process](#challenge-response-process). +The HTTPS client calculates a response to the provided `serverHmacChallenge` using the hashing algorithm as indicated in the `selectedHmacHashingAlgorithm`. For details see [Challenge response process](#challenge-response-process). -From hereon the process branches into two scenarios, depending on if the HTTP client will be the communication client or the communication server. See [Mapping the CEM and RM to communication server or client](#mapping-the-cem-and-rm-to-communication-server-or-client) for which node will perform which role for communication. +From hereon the process branches into two scenarios, depending on if the HTTPS client will be the communication client or the communication server. See [Mapping the CEM and RM to communication server or client](#mapping-the-cem-and-rm-to-communication-server-or-client) for which node will perform which role for communication. -If the HTTP server will be the communication *server* steps 6A, 7A and 8A **should** follow. If the HTTP server will be the communications *client* steps 6B, 7B en 8B **should** follow. +If the HTTPS server will be the communication *server* steps 6A, 7A and 8A **should** follow. If the HTTPS server will be the communications *client* steps 6B, 7B en 8B **should** follow. ### 6A. POST /[version]/requestConnectionDetails -> Note: The `pairingAttemptId` must be provided through a header for this HTTP request +> Note: The `pairingAttemptId` must be provided through a header for this HTTPS request -The HTTP client makes a request for the connection details. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 5. +The HTTPS client makes a request for the connection details. This request also serves as a way to send the HTTPS server the `serverHmacChallengeResponse` calculated in step 5. If the `pairingAttemptId` is not recognized by the server (or has expired), the server **must** respond with status code 401. @@ -800,13 +801,13 @@ The client **must** perform the following checks during this request: If no checks fail the client **should** proceed to the next step. -### 7A. HTTP server checks serverHmacChallengeResponse -The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. +### 7A. HTTPS server checks serverHmacChallengeResponse +The HTTPS server checks the `serverHmacChallengeResponse` provided by the HTTPS client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. -If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). +If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTPS client anymore. If the HTTPS client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). ### 8A. Response status 200 -The server **must** generates an access token for the HTTP client. The access token is random binary data and **must** be generated by a cryptographically secure pseudorandom number generator and **must** have a minimum length of 32 bytes. It is encoded using Base64. The access token **cannot** be used by the initiator node until the pairing process is completed. +The server **must** generates an access token for the HTTPS client. The access token is random binary data and **must** be generated by a cryptographically secure pseudorandom number generator and **must** have a minimum length of 32 bytes. It is encoded using Base64. The access token **cannot** be used by the initiator node until the pairing process is completed. The server responds with two pieces of information: @@ -815,12 +816,12 @@ The server responds with two pieces of information: | `initiateConnectionUrl` | The base URL for the connection process (does not include the version number) | | `accessToken` | The access token that was generated for this node | -If the response is understood and properly formatted, the HTTP client **should** proceed to the next step. Otherwise the HTTP client **must** stop the pairing attempt. It **must** attempt to inform the HTTP server of this by doing an HTTP request to `finalizePairing` where the value of `success` must be `false`. +If the response is understood and properly formatted, the HTTPS client **should** proceed to the next step. Otherwise the HTTPS client **must** stop the pairing attempt. It **must** attempt to inform the HTTPS server of this by doing an HTTPS request to `finalizePairing` where the value of `success` must be `false`. ### 6B. POST /[version]/postConnectionDetails -> Note: The `pairingAttemptId` must be provided through a header for this HTTP request +> Note: The `pairingAttemptId` must be provided through a header for this HTTPS request -The HTTP sends the connection details to the HTTP server. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 5. +The HTTPS sends the connection details to the HTTPS server. This request also serves as a way to send the HTTPS server the `serverHmacChallengeResponse` calculated in step 5. In this case the pairing server will become the communication client. Once the pairing server becomes the communication client, it does not know what the certificate that the communication server will use. That is why it needs to provide the fingerprint of its CA (root) certificate using the property `certificateFingerprint`. This property is a map, where the key of the map is the hashing algorithm used to generate the fingerprint, and the value is the fingerprint itself. The hashing function `SHA256` and the related fingerprint **must** always be provided. The communication client **must** pin this certificate to the domain name of the communication server. @@ -848,18 +849,18 @@ The server **must** perform the following checks during this request: If no checks fail the server **should** proceed to the next step. -### 7B. HTTP server checks serverHmacChallengeResponse -The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. +### 7B. HTTPS server checks serverHmacChallengeResponse +The HTTPS server checks the `serverHmacChallengeResponse` provided by the HTTPS client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. -If the result is identical, the server **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). +If the result is identical, the server **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTPS client anymore. If the HTTPS client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). ### 8B. Response status 204 The server confirms it has accepted the response and received the connection details by responding with HTTP status 204. ### 9. POST /[version]/finalizePairing -> Note: The `pairingAttemptId` must be provided through a header for this HTTP request +> Note: The `pairingAttemptId` must be provided through a header for this HTTPS request -If all interaction has been successful until this point, the HTTP client **must** do a request to finalize the pairing attempt. The provided value for `success` **must** be `true`. +If all interaction has been successful until this point, the HTTPS client **must** do a request to finalize the pairing attempt. The provided value for `success` **must** be `true`. The client **must** perform the following checks during this request: @@ -878,18 +879,18 @@ The server **must** perform the following checks during this request: If no checks fail the server **should** proceed to the next step. -Receiving a `/finalizePairing` request marks the completion of the pairing attempt for the HTTP server. If the HTTP server issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTP client. +Receiving a `/finalizePairing` request marks the completion of the pairing attempt for the HTTPS server. If the HTTPS server issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTPS client. ### 10. Response status 204 -To confirm the successful completion of the pairing attempt, the HTTP server responds to the client with HTTP status code 204. This response marks the completion of the pairing attempt for the HTTP client. If the HTTP client issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTP client. +To confirm the successful completion of the pairing attempt, the HTTPS server responds to the client with HTTP status code 204. This response marks the completion of the pairing attempt for the HTTPS client. If the HTTPS client issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTPS client. -If the HTTP server was using a self-signed TLS certificate, the HTTP client can now store the self-signed root certificate. The client **must** check that this is the CA certificate that is used for all future interaction with this endpoint. The HTTP server is allowed to use a new self-signed server certificate, as long as it is signed by the self-signed CA certificate that was used during the pairing process. +If the HTTPS server was using a self-signed TLS certificate, the HTTPS client can now store the self-signed root certificate. The client **must** check that this is the CA certificate that is used for all future interaction with this endpoint. The HTTPS server is allowed to use a new self-signed server certificate, as long as it is signed by the self-signed CA certificate that was used during the pairing process. ### Interruption of the process -A pairing attempt has a maximum duration of 15 seconds. That means that once a `pairingAttemptId` has been issued, this `pairingAttemptId` cannot be used after 15 seconds since it was issued. From the perspective of the HTTP server, any pairing attempt that is not completed in 15 seconds (with success or not) is considered a failed attempt. From the perspective of the HTTP client, if the server does not respond within 15 seconds since it received the `pairingAttemptId`, it must consider the pairing attempt as failed. If the HTTP client wants to make another attempt, it should start again at step 1 (starting with the API version selection process is also allowed). +A pairing attempt has a maximum duration of 15 seconds. That means that once a `pairingAttemptId` has been issued, this `pairingAttemptId` cannot be used after 15 seconds since it was issued. From the perspective of the HTTPS server, any pairing attempt that is not completed in 15 seconds (with success or not) is considered a failed attempt. From the perspective of the HTTPS client, if the server does not respond within 15 seconds since it received the `pairingAttemptId`, it must consider the pairing attempt as failed. If the HTTPS client wants to make another attempt, it should start again at step 1 (starting with the API version selection process is also allowed). ### Invalid interactions -If the server receives a wrong HTTP request (e.g. `/postConnectionDetails` while it was expecting `/requestConnectionDetails`) or when it receives the requests in the wrong order (e.g. `/finalizePairing` with `success` = `true` before calling `/requestConnectionDetails`) it **must** respond with a status 400 and consider the pairing attempt as failed. The only exception is receiving the same request twice. +If the server receives a wrong HTTPS request (e.g. `/postConnectionDetails` while it was expecting `/requestConnectionDetails`) or when it receives the requests in the wrong order (e.g. `/finalizePairing` with `success` = `true` before calling `/requestConnectionDetails`) it **must** respond with a status 400 and consider the pairing attempt as failed. The only exception is receiving the same request twice. # S2 Connection (normative) @@ -926,8 +927,8 @@ During the pairing process an `accessToken` is generated by the node which will ``` @startuml -participant "HTTP Client" as Client -participant "HTTP Server" as Server +participant "HTTPS Client" as Client +participant "HTTPS Server" as Server Client->Server++: 1. POST /[version]/initiateSession Server->Server: 2. Generate new pending accessToken @@ -946,12 +947,12 @@ Client -> Client : 8. Remove old accessToken Before a node can initiate a session, it needs four things. -1. The HTTP server and the HTTP client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. -2. The HTTP client must have the base URL of the session initiation API (e.g. `https://hostname.local/connection/`) -3. The HTTP client must have selected the version on the session initiation API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) +1. The HTTPS server and the HTTPS client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. +2. The HTTPS client must have the base URL of the session initiation API (e.g. `https://hostname.local/connection/`) +3. The HTTPS client must have selected the version on the session initiation API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) 4. The two nodes must have been paired successfully and must have an accessToken for this pairing -If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the session initiation process. +If the HTTPS client does not fulfill these preconditions, it **cannot** send the first HTTPS request of the session initiation process. ### 1. POST /[version]/initiateSession Since there are situations in which the client cannot know for sure which `accessToken` the communication server uses for this pairing, the communication client must keep a persisted list of `accessTokens` (which will typically contain only one `accessToken`). @@ -1164,11 +1165,11 @@ Unpairing can be initiated by either node, and **should** only be done when inst ## Unpairing by the communication client -If the communication client takes the initiative to unpair, it first **should** close the communication session regarding this pairing (if there is one). Then it **must** call the `/[version]/unpair` HTTP API endpoint of the server using its `accessToken` (after discovering the API version at the server, the same way as steps 1 to 3 as mentioned in [Session initiation](#session-initiation)). Upon receiving the unpair call from a client, the server node **must** remove all security information related to this pairing. Then the client **must** remove all security information of the communication server related to this pairing. +If the communication client takes the initiative to unpair, it first **should** close the communication session regarding this pairing (if there is one). Then it **must** call the `/[version]/unpair` HTTPS API endpoint of the server using its `accessToken` (after discovering the API version at the server, the same way as steps 1 to 3 as mentioned in [Session initiation](#session-initiation)). Upon receiving the unpair call from a client, the server node **must** remove all security information related to this pairing. Then the client **must** remove all security information of the communication server related to this pairing. ## Unpairing by the communication server -If the server takes the initiative to unpair, first it **must** remove all security information related to client node. After that, if there is an active S2 session, it **should** send an S2 [SessionRequest](/model-reference/Common/SessionRequest/) message with type [RECONNECT](/model-reference/Common/SessionRequestType/) to the client. The next `/[version]/initiateConnection` HTTP API request **must** fail with the `No longer paired` response to let the client know that it is not paired anymore. +If the server takes the initiative to unpair, first it **must** remove all security information related to client node. After that, if there is an active S2 session, it **should** send an S2 [SessionRequest](/model-reference/Common/SessionRequest/) message with type [RECONNECT](/model-reference/Common/SessionRequestType/) to the client. The next `/[version]/initiateConnection` HTTPS API request **must** fail with the `No longer paired` response to let the client know that it is not paired anymore. Client and server **can** keep other (non-security) information for, for example, user experience purposes. diff --git a/website/static/img/communication-layer/api_version_selection.png b/website/static/img/communication-layer/api_version_selection.png index 212fd6b8dc26d811f26fb7fca1c7421e93cb1462..28e21c74d02e28627b23c4a0f5999cebf0d058de 100644 GIT binary patch literal 5891 zcmb7|WmFv9vWAfW!Gi>spuq=1fB^yo8-m*m!DVoFcZb0OL4$q_32s4x1@{EV5S-xd z5@7JT~m#hQ*CW+eSOE5FFoz; z?aj@79Ua4ceSO{Cqy7EkV`F23gOej8vr|)3i;Mfq%gZY(N2{yH zdwY9_hsQ`H5`{tuOyc4{#Kz?zuj^s%?BeTSY2|^YVCiJ(X69jO@eJzw%*MmR#a#>l zaB(nm^6+$Y;4^o2^co#zeAv}x3)c1c*E$;d!-`LiV^SAU7(YSOkw2{eeH1@B9Ea!; z9Q2iB1&d-TlU=6uW@;1t9>DmBvzN3g1Fx}$ed;o*#j-diHs6%oQ(J<SORpr<5_#F$1Ty!eTI-DvosBv(lo1Pta_ zLVvBj!4Cn!(-gQ%iOhsnR}r#jdOkM=zj^(9dl$fGoCEi-2jws&<+ zbbvH6*!aQ)v;Aqmf`56sws z1A6JL;aVxTsuI-2Zpi(O*_K$=XKxzH0XMUq*Np}8Nf&?@pXyrW!5hUhlgHWgXZ(7L z&EsEF4~e2yT^kqb8)%A;buYpr6>s82-IYMrCqM7&mNyzd&B^t;eEABLKe6mi2b6} z=?$Za0NdIG-c0H-Ic?7GQdG4KN*=g zuWIe@f|10PK*4YxlhA%i89L(O&aMN8IM|33R^tawTm~KZmC$7^8bAC&14)DYyKy>? zJ6{m=9xGlc26n@Qw)j+JO{^GQErPPG#&yuJtiKE^aRAk=q7%~XSA44EqwQmaX11n&$K3)4 z#CD|p1Vw4o_G*jkbaulTYJX-7IxlOQv6WK*k776U5YIu*oXJ%;?f|M-dVZ(kSR_C% zmilECV-PC(^4XNr=qLlv!}-t#-u?dQ8c<<6i2*Wxb&D7RhiZ@le@+kt|1b;0Unjmb zJoko08XQfgm3$xVs%QPz(&U>SU#THA|$_;Om|?eBFZ z2Hc_RAvDQo8Z4w}|4xw*yWVt_(kD{RWE>4OpS4~SbAjTE7brF!?fBrM2E0r?K6>i# zR3`44S@vUV-8kWwGeW0BkTRpd^x?{~A?FYDTGkFZKeP92A?_XQzz^QqE4hl(nAw$| zQe5+va}~wJ*#0E=AHPk)(ERy$a$z6q@dlEe<$BHIQP#km|JGtiE3hTZ5=pq2RU|0v zh?86U*MmytKZ(2@uR8zm){B;J@z>*loE`19_6mNSX_U-p<+KKmbK?x~CdyNoe9fd+ z4$#`(6{g2`SIZ_IaxGe?>}BECQ5xc3do~D?XNbA_BTy-1;)vv~PEcBxvZ$xVXuotM zJ4-4dN@mJ^rYf8e+fq&T!Q$1U54FtQtt1E6=A7=bjq;TSeDGJ0x31FxQGVk`2>Rfi zU7^9R>%(r|ymvdl8Gk9ppqc|P?@uM(y*EBx37^O1XgdqPd{$_gC9b~|uQIY9L39^v z?P@lM>khnOX9WpLaU%zgk{6cmj|urVqzMPH6yASI*LCCZPOSb&ru5TXV?w2-<#cHq zbbF_%;aI6P-0#%1kD}#+IF=f-w`r)ISLCv>OtrziR5$0dq#2PFR->HPjYfA@`nSo7 zflb(T;cLfu1189&sIz@}uj{M6?_I>FYycMRQ`msWo%iy=ccgy$yyz|PYl71mrTdo= z%dDQGXeW*huF3n(&3l^l@4lu;1&WjgH-|G>sHzZj{Ibt%U69%IN=Cz;H(Z%B=rT)_ z(#no^a+qf`O}iFwE~qC$BW2El#>{d44bV|gBNbt4@1}9py)0gzs!YHt*RI@dtqZ_dzPt=$7^H!?(|={x7jzRI4SsWYo7> z*of82v*1m^grM~U#TOlt_W?$f1p;5~(Xuf?_O6`Lu2n^^vd+@&c*oVSd~14c;uDYq zrSx<`n}&(SQiU!SZL4Q~eKBGo&TP^QHqb;R{v^rPC(bNGeDY?7VFiA29??IfhYB}? zN0C^!IGdljhK1?_FQ;%B^iHYfo4`Z0beXcTaUy7b)mS5IX_~2MwXsxde7qqW?fX=RKAJX{oke)*9vn3f81uTTn*TnKzKEPV@kc31dikeRH zaK3;2+2vKUq*O{%&IiMlnyChiJ6`xH0GRXOcIKg@mHAN9hPK}!Mi`KG|u8^t9R zHMJmZF0gUB?kt0F14X#PmqD!aj@kyWIAlAjzsFO3A|Iw`(Kc`_nEkWSk=j(MvPKJs zp~K?E_FB}A5z$%K?6LNc*mCpDDK6_NhfDCLYXKe1T07X<58LE6rn&`%$F>mHGf_V9 z6@6;_-u`jW_43yR$RH7JU$ShpPa* z!Af24U~K?B@))@|6OwgoS<_=N{6aa$_|8 zGeTOgMZWds+~o+ImH+yh1`(~+;rB0;7;i<~75PH9CSG086?gv9*Dk$CA1*%qGBel# zpQdSJiQjA1&fqr&V&D?@0lU1a0w4=G#CViy`B(LuHayp|o$?tXS_8G9M6K-Kz>h%O z<}b{zy)j$idwH4gn;$J$lnbfL{B@ta6pmC{?$vT^MV^|7%LQ$G>lJ4)M!qg1txE9O zh?>tK7yX49a39Jv#lJvCydh-mq2x;Xj_?kOv{`E>DOhf4ku~j&q;?$vRUvuXJreWan zj$;Cuy5IfDJ}=$+H=k3OdQHA!K}g_!T>9+8nOP{N31(|4`zYgCxUG3sF0HB+DG0*@ zX(I+dXBH$6lmzoh;;u9cfW#ZK*FM3ML8)zg#U=ddz4?S>E|&qA15eYwDT zQ3+3uCWIdb!_Hewz(iH&>j@<_-``4vbR}Ie9LzW+W+!5gbj zn^XQFQL_PDqBilRl-Sjm4^ z=OfqNoK6S%cX)HQ??LjaDUpKG5G)o2)7vYurZ&f^n(gx?E=jYnrLZ7YhN`zA*>b}& z^fV+8Pv&wZ4~TN)uG~*}=1VU2SePo7ww78Z;72EZ&W4ZG$$IhdJ9i%wb8X!r)x35) zIY{odcw&jdd`Wnnv7=h|Lu5_E_?dj}iMT zKRR1&VO~Y7EpL^)NrZVf!t#mH%)3H_OC?M{AOjG`Of!b>oan!`li%90P=sA#GO3R~ z4gYy%U(QKlaKg7kbp9d1cWj&$3AowcMk4(Jf_yR@Mew&6@~j-7@1+Q^OP|gYB~?@H zZ`+t{+^vgd=D+&pB2i;jpYioaU)RB7Bj*hUm7Dd&PovWW34@{-=TGI)Ct~FOt_vaH zdFeoT60}`z7)A+6?BC6kr1^X0Pr_`!u|Li*jRiHR+Cz@Si?2z`dXVb$SsgSvk<{5j zkJFYM7X(HtelI`|%>!DXS1G%JTVN9z&&&H#mfvqNj|7DmMJm7g)K)Kjv75v5tF(XN z%2un}7SkPx>8Uu!@U1(NAG5o0>`TC;wOC}uaLhdVti8Iladkpfk0Mh(UC1kLq#Y+Q zx-k(h=_tToQUg!3itiv**3Bx_F#V2V#}`+@d@2`)?yqFwanA=I$_4|Z$FM;M?Gfwt z<(K!#xfcYP^f*}oD4^GgtQd`H?=P-kpiweS{!)VNDf@`>6YwRe);09$lGMz362XY; zyCGzfHlYjOCx3soMSEhV_o$_#MQaC|0vIe_ z_F^(7FxLqesJaLqr##U)VT;i0Lne)!6YD;>_Be6xuifyq_?dasOLgqU!slJ^g+P9> zch9=~lC;}p_hz6pj&XeD&QdC}{ZR4|vlVYDE4kHYvX(Z*-=yG?jCTal?ZG)MmrM+c z{;S~-%eJwZoXFhhJuOA~#;IHH z(VhzpzSj!`BxutTh)_WyVj5kT-|}3Hwx`ta*GpqjVqo0jS5CUHs(;t&hP27oFgx-# z!P&vwXimcwhmyjGNkLRL{p_*ifsXEMvpa(hORm{Vs6y%98Dh@3Ntij$nxkS}=9OWw zjG4O4n!2Vz?Ia=MIfDkRpFHjK4BdS`&E;+L0#A#?`T3hjlIfjey)Qm;7r_TwluAQ4nq2`(LCIM?^X}!i{ej2}g*&>?3o=Z*V0lJUSheS(|Xj zjb>R5tYw#@Qv7rSVK4aN+#=G!<7#wYPQ4+cGZiUB4v+uj**`hRzuL1snbN`#`zH!FH+1K-M>~3GG9uk*#?xq z42yhVAZnWCLxm_3yP;E-kMg^P%QLxhYGZdyhnkQO-NFYXwRq zu}Kfmh}jAIKcGd=A?W{t7JLY)xel8=<$2yhE~&n%sHOda=%Lfmw8M`c-u&cA`K?&1 z{|{zS!gIYSB5e4Use-^Lyw{9WLJw1$VBhKpyNUbYQ~fzGR3Qz7pXApo$LOCQs!~Qf zKJZp$2-tuj%;L4WB=SF?#gJ6QWoOWr16&;Ul-{+WS&lM3j}OXBGOqs%w3rN%uc1av zM@rfSba+XC6IGl6ZR$yD3Kmr7^4yP1sj)3W6K;znR}d;M)Rr~ji0^O)0Yo&fVAJQ> zY(~L4eNTclEe$ek^ta^kmQ`!;g5VwbXLkO6FSaHUJ((N|i4+{pumBEpFEYw%?Lya1 zI{4o|EvQ*6Z6jSIH>P|&bE%Dj9d0YHI&ah`Gl6+dCK8|49G58UpRt|N`hazZ%CJWL zp}sn2u?PdDqP_cH_#h}w{Jibzow5!TJ7F(+3B@EC(~w!OPVcPE4ZVHXLq5Zv7YBsdEM zhX9Z7JNLWqzH{z5ul|^x>6xCY?x~ut`o(B#s^H^LFMj67#kZ~S=ocZU|U;9H#avYC-+D3@bLHd4+#ni4+{$m4ULVC zj*X9pCMISkCnu+*e0-G5%%Z%!isItpf`ZEO@`j3vimIxn+S;!T4GmwubT&74b#!#J zwjw$^2N4KFclT&t-{jcX*x=yY@bJRy?Cj*^+T7f)rKP3$`K{H}gC9SBtgjz!Y#i_J z?_XYCK0G{ZbNMknPK?`I$=Dn0<{sc;|H>Oh#opE4)5hE0j?p%N(b3!6-AfVxaCfnB z_4aw=!UuMH<2yRY@Hnd1N#EG}pMDh7#~%NjH;Ebqp>lW;mtL`v&`AE@sh_NY{I93! zbolrM6P!ZPY;aDlLec8X&qadjt;~!KUl7(Sd+U{EOWISmsT)Au9kKA=6&x@>_#@{N z=R;m-;;gp;k#E<=7*pJ33_gY7Qzf`nnDxZsWpQ}N-n1fzctEb|wGC$LMZhGu1ywJJ zt0UYgn%`nLEJ7SFLfJqlA=aa`{#YdQrMRsIbx27BGmh}vdox8rf=zvPLOWav8p0a-+%Ei7u#s*W%K*`pFv%s@SeP=>jT5iD7Ed)*{i!8r+FopMR-9 z)Jduf0nbdt2`9>JL94v97WZf9^KHNUu(w!`u-@AA2=qM=r@i#V%Znkd!{tKZ7O>^= zy~0l~nqApKc%20_{~2zHx4dyLwsTsa7i$V|*_{6dPITYCXWzH+vJm3ZPw<{;=^;eU zc2-y#u}X}p*&axcqXn^*QYOlH=4~LNfxrcyBC8^tgEmrbW_qc-o`sEV6PduThf(e$L! z#TFHk^z54FD-k20U{v-Ohv}Q>v@hv7vY33 zFi}o$B^z*#A}hFxHcPEIR0i`XAS0!scwUttqS=!MDf zjn--h8d*nhtl9Fe`G<;n_Tdd;Gza4RM)|;XMN<5;s+R^a4ZfIkUd~5cqoSOItmTH= zZ)inozS?`A)7?IwcRew8Q{#C)fTkrNl411=eDtu#ZHdyrdmDP8c^qv=7Wjoh; zKYjR<%d)dDdMa4DyCw~C#a%qqEj*7T(sVfVlBb*h*$SdNnMvW!`uh2ws3BT0p(v8t zm-lwCP+V#9e!{x#co`C)B<@)YP+$T9{n`bJO=k2KSB>`{gQsiWbqJcVM3>sr-G-wm z*!Q_AYUvA!6MTB($uSAK#B*9|p=s4+>f##Z)}X~`D~{UR*j!3x-N(G5mI7XhX+7=z=)2r~6jAvLm>ET2pms5*gR@uuk_TzHRXSos zajkqun)ODuj{nAiB!_}7ZWSg}YuBZdwV0;Saw2k=2~}`Fe%Q!)mvMNSF?-n&_LAxS zXqL1biTv+h02cWlQz-AP^xVn>-VMoO^bc|jw*5IDROrx=MQo>%h=#_K%9pmI+?2Ov zG^bLR64r~fb_$lm`}G4=13Jw8W29`Pf8%RxMsw_Zbj(OtE|w{b8!d@KW!Hw=aJ{@a z*`T_Z1{i}%GKtX5u@NT>($};hT7z3?64*oJaiY|*P8<;Zty4~9|m+Mb6Y~Rs()hy2fJLD^6$73Gw;07_evXeL1jEk7-K{|d? z<7`Ta!N4c;yWVXSuhmal?MNOJV$|70450N-J7;+ZV2*ojIB1$@?*8X{WH?JJSQ<*X zwu)VBN-Kqo!{kD(ZYW9Izqi-*2YYfAti6N%3@c1!PO*Y?uW3mx6w5f-h*P8>Ewl4! z+yH|PicOT?*3Ur-<$xkh(lVedkll-+PxJUf_9W!bANyqvgx<<6N>rHVn``r&gSVDD zQC}Xd5XbdV|I9aho<%MF)v9%h<~F?h9+NDC1sYU|b_oZeCQ?oO5XRi+tP2CiE+et9 z{C<98qIGPeG8$!KC#F|@;5rjOFfp(sc+>G2mFB)%955ZTY{1RTG4D}R2)Jb5dj&s| zg0A(fvqd+eVYfD4WrJOB#rk{YpjEJ6JQ~4Su2sn8x^~S1+P4a|V5d{=%ZQuMD0`=u z;Pa7~p76`|0p7yfQ#4uc;~xr!LS>^I_ZgvDCgVM0MT$%lUY+e|}N3yd9xS?Q*4|!8#85HM?0x_8;y~=)xNc0GSO<=4U4&(+#J0#Bn++ zA8pvMpX%uW%1gy0vP(qRyq>MW<`WyywMTuynWoIoG;uY!+j!E+@Dgpvs~>a?xY(+~0fg_s5f9F7dU> zrm8W2^bZ~jgFjumA^_xtlk5?^U)M>Pox5e;K2Xz;LUrxg(BH;sg;-zdMX;x>;t zAix(rkC~g^rt2$oJ`=JCb_CTr0o2082aU3l$Q3Pk!La7NjY_#!@V3!qKPP65&>`&^ z!{q9+s%~g6>1m(rY_E~D!f95YJ2dR#c!pFpHzyq#vy@sQ3CNAt=WrAa;?uGQ!19k? z+buS~vX43Hu4Zj`!gib}R(?YD+7J74X(dqlgMyS^^gzN`8Y}*bBl0EQC)Ikfy=5|K zTKn`gl>#O3LbK2P`nW%&G(HhRChQa1MKg`2`m@4KSq+WJVxZ$ITf!3b3NRL5xk&3NK;2~<4v+#i0JyUYD*HP_>m~ROBa&j=m%jo1 zy_r2GZ|aR(27<{oLB;|O$0E4i1)cZL&%2!atMm1FWf~EYw`Bb^pvDWMfRCOTt6Z~p z$^^Iz(3X*kbq4rbj+t1kcJ#|Z`r`fi_webvVA1wh7qU^{&vWXtJWO#gvVX>c; zR1DtXUb`k0=_|8zV|Q9V3wh~8il{%=4108^6v$z^8;?^u(*3rndJY^)18^W4QD6D~G49mAxyty5TVWJfTgV@Bf7VjBA05Zi#ozaI*Ir#8>9&JRrF5 zZ7fPSpoT$Kq!a~dM}*qtp{9654mv7|6YCheeMeB7c-jr7U&Qs()WX?EtK0l!^mCM~ z+GR35zHc#o21Zy<6oTD!M8~wL9p3jjDk}Pf8B3$J-6|?eHk-BaBDCRBbr(LCoPSz= zpN$H`Kh3gyjQU72o;rwaIQ zhA=4pv93_~aZE+L@-g-m7==b{Kf&{a=zg&KZIqeJs$KRp@~e0oFsiesauRqbb}I!g z>WXmDW-|5OnK$Dc7AoO{PGE%yUiUjwitCs@E=Z5v4aMNK#pBl#_`+vu<1|EX{+fY_ z+8hgm%sviMbwSR3iWlJ#*>}$ZFxOsX)U#Wz+VuH#J~XHe&NQn}S=4;d@nwz-pU+M; z_JYFTkXL_;N^PO~P<9zA_Wfnwa|q4RK%Zj%0FsW>x~xmAg2;qmO&D*VuPOIw`hD|6 z#@a5%5yuV~^pa3!CzF6MLTqZDk`?NM8`nB|D7y38dX0xexZNv}+o~IB z^7R>}YdL~6jzVDb);$OQkx*_D0`OEFv?MhZ8afhApp(GPIu_GQ(n>d%XG-FhxQ<7B z5u-fa7cx0~`+RNXyg$uuoaJqJSDm3S1bp?5t&y?GD^qWKp8LO z{*3ZPT|Opj82tnbp7N%+vSUDzNz$MMp6|rx1T+KMcHy0|(v^)jTusrY9< zFHWe3-J81#Zmo{6?3i3ktm_#+xVyaGbyO4m+W;Oi2-yE=V?p+)&2*j(_35@}vA`Ts z)X*jVh%2o&q)5jd-STSi-cT`-|0=uBBR)w^7`JMdR{0lInLr4i7%i5hGb#*%iRnP;&8s9KCr(FI1+d}_$-7o_H_%A+ zmw>fJ*l_5d{@aUwvA{%T7NUH?*~fo`^Eaw6V5+7(9p$9O9Y(6Mh3hhTU1Gkyd~wn_ z_y2m=F60XBHF+89LQRk}cBEr99nmdRBnz#Jb8D+t7d|j_DZ=~(OYfFi=@kFSS<#U` z(mTolO&4B%(brPKT+w>7_8t=UZh~ZpWF~8CZ?5|10~1DcTRoI3n14Gjl15C6A(tKkCU49*KDO%V{7hz_c<6u{gco_Gw zDgRZ4HnszyC5NjG8f*Hb`yIAWye2{LT=D8Lfdma3{!0lq?VKB`thoqM6ewi_snj3*Z9u_)VuqF><%E>d z2=LnURzm_9EfHaqd8aYkvjuylTS4vQ(3B(lb>ynBNi>Dlflc6oHts`5if+6LgM`+J zZErz=_w3ceUz`E!BZ+E1WZS*@%4DAI#+_H%DeOSf?M%{4Bo23yE=+?-S>RI%>89xq zGgd@f*D{S+KGlH)$&-I`hHuYGCc;7;zTg`4_w-hFz&~XT0KT!B2wUK*#t$Q+@l_{q z)lSF~rC-GKbH0Wj%f)Vi^Bo%}gb5%(_nXr6rxsRrH)M^DmBQRRUdMTUTrrZdxj(fX zc`%sD;VqYuQk>)4BoeW#k7_6)Pkz41d@8pdj;-yE_)OlZ7qq>SMZaMnhU| zmD2`OVc6-q6(tFc0q#i9leu|Hh7#auuB5|fXIi6grzNQRvA4~`GW$9T5F7#dODj#S zXEfV-)F;3rqds!NfD}*fg2hC1>XnIknv~JT#?4mRv!e9!P*fTvAkk%VAi%tMsh#X^ zMlqjWMPmp~#W^{_p;~=$pb6qEG4(OF9Zv71icj1OrQH3n74^P3s>Bt3zlXq$Hw+vd zbIMe_IuIw>G}h`(0}0BUwrOw(g0`oM3`OSz>0BepR?=<(KuJ DDg7&Y diff --git a/website/static/img/communication-layer/pairing_http_process.png b/website/static/img/communication-layer/pairing_http_process.png index 37babe92a4d19cc7ff0f49b562cd3c7e2a592bdd..33a17e9198e9a02f97b40992e36c4aafda89a4d3 100644 GIT binary patch literal 29541 zcmbTdWmH_xw)Trd@Sq7!aCZm?jGE4ll|XkpL5>#ez>#l^+Mq@|?=<>Z9rf89^v7BQBhIh;R#Vu$x+cBF|l5WNq#XgF|n~}scC_k zS)plZY01gCnVChoc@cnu=)AnV?Cj#g!s^1J*z$^`ipu2T;^LyBn)33d%Bqy=n$*U| z#=5%B#>U>prmW_coX*b9=H~v+&XLZpg0AkuzW&nw{{Ehx@qvNq!J+b@;fnEz+KGvY zp`p34v89R0y2+{fx%uXWg@x(qjfI81g~it8m5#Obu9cOQ#l`*g_0x^bp6#9f{ez+1 z-QBINi|y^pgTvwTi;0udsq>G|<;CUX&F$_ z7jpwcdk`T5O9NXSdjov}T^9mldwXj;4mvt(a~(^22McprJu3^x@ezFBwAPx)E8GA3 zdk|3IeVo(J;w9vI=#c|29?^ejauNXKdH1?NZo5RjZtXh<6Y>u*7fXcZb*{&=NH%!>cf9i^%fhRW;Sh{!_iQ^rvY04uzG^3|DMHd1*8!$z$=d}F z(}xK9Qn{GMZ=j4U6`XnMXqK;4%NgHGW9SRs(&RLH}t2f zuGzDbJ0m_M$0d_7=#%ll(O@S)3Z4zzk#qGT)8ba)cY{n;gH1S0g<{a$ z7dHHGL)=};Z!V5IKa|AcGuiDb!4ZLZMC1rfP>d#1y*od+R%p$}68g=ek5jxvlLiABE zWP?(ClZjSrd-9dlTz*IlbzHi=Zm4(~Nhnp~n_aHx5S`opwjhcP+aCt7=F0`-$^^p> zvwY@&g@^{#<<~}u6(L|6?%r5Gc<&S;DkPAlChE$Egt@R*!UoZ#YAk8yP#Z$Aw!{FBNw?agNL zX`#ziUF~i(LF9t}+vTY8IhQeFD`vO0-A4b9%{ekQ%hn!H~w` zGK;0Rlbj*UdvsAu<6UrEZ1)l^Chz*se(!zh_}xHD6=>Z5p0V%$uYPWw*uG@;~{4qFVFQ( zG4)O@{wr2t=O0Wj6MXAx>t-f&eyxnUIL@5>tgMF(4TgeiLuWy8j2OtH zLKg42$;B-7#Pe5Vq_3XMhq}p^61z{5X=7JQF;#6?<|*{Ta%vmAjtNc`DMy~8Qi_zF zsf@b!Z!&%vnsnUTx*~LHtus(9<>eK|tTkSL>Vt&0(n^Tu#LG8AZj;2MpFA)u-Yj-o zF1xKF@#wtY>(4ry!hO>q;Z?N^6hm{M0%4saj z*X9)!nX%bTh6x^w9Pmyr(sLQ)s8CR!xCCbo;*j2HM_U?j>Gh9k=z^uINs)>Gv;?<<(YX;wv>ZRZ{G}Ki)sHv$;neXD%#0+|1G~f;mPntjD{GL>v zx}X?w84@aW(AXE)ww+w8u6oo?#%pWInVKwuJH_b=)@N7dcEk?za9v#fGca#yVIWu= zgS)#S@!Wx9XWHPn@Vr*6hv7p8UKSPAT7m)kXBmD2np#rZGFhV@Vc#2Jdy1YPVF`x)r z;B|GU2K7Ci{EY>uP`f$Ruc%PEX!{fuI88VfZ%yClwb;53D+|N2kHz1i@y7)r31T#I ze6UP}ZOS^$a;L1MFmJhBj-}BrVpAMGd04v*Lc?p&mV`Nge1fvPc<{~v#)rQD)*k_i zP$fDjjMeok&;sKg-<2QS+T_44Gx$-25{Uo3^h$?rm*4n6HafX`Q>0dH-bEsP5tV`9 z`>h@N4QA%=g+4ylnaV@E`wSPK{)*N0vq92U)Z@y5i5caDH_e0?oRIb+^$(W0rxrO< z=SES4{CRZL(d9Ly`|rX>`qxM*8%2#YsIINgD3;bRg(1HW*G+K-y3is%nh9V-Lb99z z7~ckb_Gwo^x~20wluVu3VUj_P(# zF~TC)FRE{Nued`nMAD@jO1o{!c0@xY+NGs>Z{z$Gx>js75%hxUb3sMFev2)2qMAn{ zt#}n-Hk?lEb&ycpgtY8^O!Xaa9uv|g;uhSqg*+u&Jj9@_dg{8aKr^;~H%gCWt5lol~JKwZ-dY#I^+H0sCgIN74kEM+RE=G zQ>z@EnALL2waH7Cpu`oWwUdBNxY4tkcKOS?_Pl|ldgV~+&eQnXwL7@hP0dSL8TqoN zdkfn?c-!RjE9fC&Uqq)eHvn>=tY4(yEGuX?xDDm@8p&MXB_xA|2ALc`OX2UgWShT$ z6|FJG_yrE0A*?7`cZHp?Myf$z&(lE~s}3kwq?I2@VEIm4Tq9g>B1E_iuiJ}W)@@R` zW>fuQ#X2Xn(?7a{%=K|C2AO-EFSc`t`~te6frd|SFW4AVsDa4c>AqlLLU4RY5;T0Y z!*4WJ3J84j>mTJ>E%E>fD&*Zfz4i2Z>X!q7bhQ4n0zQ3%W1NO``I<=3MT`jhkX^uL zQe($xYQL*6Y#Th#a0>4W%gRw8 z2s7+~T}JPakPhJktGwg_>r+;>>{fw<^e84DQ+!;4cKc}Mj>?Lr1l0`42c3u?8X+V5r{TnVk~8&_$% zf=(xeVbw9mq~Z#F;O&6H=zDXh1Wq?C(cqkrPoX$r( zyg|BF5)<6UpQ=hR=GizTnCAa(e!iVcqtw?A=MiW_|CO4=VM0njm!Q zB);c>Mnha6k{a#;re;IhD8P~kK9d=|aM~GMj@hPYrO(bCPZD4Qy?2E!#}{`Ut~0@XhdBZQ0KD&cO&+}0EcfWt}QDp z&LFx(t(78*ok5u&zpD>0L_d9v&0+PN9do9|($Wqkg}(`*2Duv+AJ*8p^o0kSDe|GP204 z7BgJZwIlR$%*#^*HXzE8m79!Nnhn-P1i&OrwzVW;lyYCNGn2$>FP<)cqePv9{xNRu zV#3RIfDVyG%MbC^i+H&|m?{)KujTBU%s)mTCK>{fYF8&JPoN~mC||>zu$=fUSYsGV z+4oDrsyb=>tcPzh_VTqtQVlc<%BCP8mBvr)oARmV*xau&U_O{0nv+yUpTJaz0$>n0 z)OG9MM*HB!Wqx`Wg!w5gJPOzq4WEOm$cCaQyAjskp7X1Ogr?@3$^?og7EIRx`jtXM z6%eTT!bKIzQF8VZAGMhMEyUOjeopoNBtiKuO9-Q4LWJ^b`dQJwqHd~bD}7m7e%x1~ z7Yn*kJk4x+E4HM5O%JAk{9EtyKq*xrujZ7fXgR>7|HM^fE*Cx_*WSRibT(L5KGt+@ znDNh);1~?Yv>hsgs=f*2#l*wbt%Cou5h>q}FR?c;FP%58ujon?MP5d( zP0E6co+)aI=PZgD@^JX3WVlf?o?v>0xPbV0#PSQ5wpjJqK=+eCEqUls ze!mAF1CS`s8vVq2|@ zp@gNzGCRO>J4p^tg4@`L$_?dwv>ouijmqV(jMe_YrLH@IZRA;;fwxA1ANFowN&>LW zH~Sfjl2EN$XrBPCs$<&@r8qGJyrC%Zz>{!?K?YU%+S}$xsBWC?yoxNMs|0X99klq* zrM_@X3hIie?CA2Hh<B&9Lu8F1Nd7|ToIvdfNf4%i z?s&GkbND->BraaH#xa1XZIX&g#^;xfXmrjrrG|(F0JM$;t=u#Lv>&j`7uhF|^x1us zLy6;Ag9W`GYLEI$ZRkn*9^O~nC5qFV~l=y z0|b-CIT2A2D$6)Q{!)AN<^s(4NZW4oob~0#5<3H|nWFT)>YIxOdu*Z)1=q}|kI#7& zydUHD*`&QOgey6BNFm5b#{Nt+*9=4NGL#mmS%EXJNgMiUDy$`&S5CYOO~O7`k`v81 z6u~(=>>qvjUm~%X+&B4e=Be{<1v%tTDNv0F%FsLMAwyj7K!tT4@jAhQj0z7R=Wap% z!Jj3s;f=qI?=1+-jv0NP*fZ6FSF%KaeIq7~{>1zn+>pE$82vapKV!ZOC`0=y`%$DG za(1+)b_Agq$tda^7Qmf-bdXAxN!P=ssE9@K(+e~M)z;z{IBpSg!ymD z3{=a&NZNp}QS{tKG+e5XQ_2b6uq87zeJg4WgQ+7ZCNTE(nVGVA{nGRPHXxP67aq-g zBKMiUcy*tRCGgk}*QlY*9+ISsJQQ0xkx5-(ZT&c% z|MX0;&I(a!APb^p3Tu+SZVJK!37+yEyMb96yIP5&svLxzrBDQ&8AE)AZNrLZwT8Ni zLnWp!9d)Ca2l-90Ei{sJC-PCe3AlkM(ZjMTG;W~Iymm~?)%?x&zQ{w)_1O*Jg?gnWSv|f z#Mf<(LsFGKyesbB7mX00q&CuB&;Zls`c0W*!p%SJjrFbT^!W{->{7UGAU)bdBo$(s z1Foe6ArL#YMMiiJtiP>`p{zs>rS>$9p!R6hF93(lqnz=szYCSd|VDps(4|A;r%{A=yW z3lu&5+Csp+F0Q`l(3Wz=eo4XvkWlTj;dxUSTIlP(sY zXj?z4s+U^(81h?41F{lui7M$^1?JS*fdcy?iVx-mqe&8`)&0UU;bC?u#i#OZ$eZC% zsXDSN+-qXk4M4#%oBsL8fx|sc8r_>2(UVKhRUpyX7z<)er&_waVLlb&{cM?nfk4q1 zdkCV}a*c6Z<*}~Wzv`1+t?^;p#IjDJ`=r_ZPxGM=1>1ok1dS&}OAoVhkxQ#aWAg-m z+FlesD40OmcE+wLAU0hDz8&qn*;gA&f4tq!a#aBvB@?ZiblGQS0 zq3o*4m+$z9;NgA|xQ}}el|1hruQ~9)!*4fmkG=-dVkS(W=-+?fEvF zlgh1unOW(?0wn=;F|1r~!5j#7!;+nVTK~X{m{o68B#Xl2JZQAV6$ez_kSsk8VKB^H zCazeu=lagQoGx<&pS#?ByG{RYA@aVC9fk&*LsWtbLy7@TSU_$ajttn_bjwr+O69ci zYXI5lO}l>22yV|kDoYYE1*wWn2#PUAU5c|zt_ZqO>?y$zeKDnYDr|T4c;z?)$%pn^ z#9lt~r#$>Yfyc}+qDO^5eA6LP3*VRxM-pI6!vmPgVjPkh-ry(V3Fw1{2NY4Z%7Zf9 z{am9#O8w8C`zEx(198B}51*Ajh1{?ZNb3 zHv%?Jl@AyD#rIg*e_3tcYYMr6X78s5S?jJ&E><3A!zypj;Y#E1up1&xquFg9^=={W zd=}$~l*sM!d5nGq@_;sjpdbG0w6sG_xO<(NQ>pjV9YY#rCzpu$h#dX;B=3|u3LsfN z22jtOq`2x2T&y08qV7hE#j>umr&D$Q1+mLyA9)lFTkxja z$!zmw;5;6mrzo&R;BZxCu^uVJr<{HI?WQ_cjor};XcDv?yd5te_gXElyTIWxSSk}? z6Y0O-O306q$b^cN8#f2J+lFdb7A;0qW_IwF_Td#C?W*%0}Y9U^mH zis{YxWO?|2*JWPL<@*w!&tnju726S@=m{Hw`6X4*z+FqK{LGspZGV>94NsXzj>EJ; z?g3bO`N+Vhv#Jbu^SHYe{x&H`$P#~QE70#b%PLXWGh5~sW=Y&>TGmIKFM&dL@Tw8e_Az ziC-z=UgDMCuetrht3LMeAI%ewn<*hLC^(~&{zTVrbu19lD})?P|42*q;yHDcFVjWE zlQ<6}2nx^JTcy5O=%gOdaa#S=Ux82SPCH!21i70{1qt%qvPx}OO5{oI*CRzSx7Y!O zl;94~q>!1R&BIhX4$+$+?(L`8j!5dM$Z*5SXqHP5jo6R%Ca?KPH6I%qpeZLXr}<}} z%5GgzJ{cZ2+*4tYRNsSa$&ca}fDd9rY@X{Nxr0f>sxdk%_O=lIk6cY+e8rCfWB0B} zH+wqhw{!_R7)5KiOjnusa@HvTV7Y3d-?@A%IjCkv5!w<3{snnC6f?v_k5{j!NoS}wUBsP-2(TL zp<7(bf+))()aSvo?v!-k6d-Z;A}QkY^4Hz804v1tQvCDL!@mwHb{u*1mYi#h15( zX@?E~cjaM(%qU2CxI+;Pqc*+0)_>)xK(UyKg+SfZz5^~k<^N$rcKt`A1&*O1C`4onX$HGn*Y_mL3U`(ixOff%L-9{AcGwHg|P{ByArrH2daOAjtU!C}ySZo0K+ z&(jx|?*r0k>)tgM>Hqlu`55Z|k*EVD z^BLn`_my4{^~10Aan@p1C7dWtuJjB)Zh{F5rf|I)S04Oqx2_rnI}Z~^Z=a2=7RXu0 z>inpQyesNQL8&KIhtn^FxeYEZ6Yz6XMtlnRZjmRMvPBDlqZee+j{J7&Ls0^0ho*zL zb|FYL)3#fy`_NP#sCZ2|!TX0?R^?hyX~h3xXx}5Kb*8{odn~`WhX>q(LfxL>r@3+2 zAs4rX_mg&B&zrPK&#GJZM?)H+ibm7zZ5&ps?A}j&y^z=%h`z_;tCJmNK@0e4(GIR@ zbqm*YjS;wx6*uF#ch?~7Ur84Lg8ftd$kjR3xeE`x*x9s;#v<7`{XjpsY@_88H5TtK z3r5hyMdH)L`^^1bRGVquEhZj5{NxgKnLAI4`$KQuaNR=6%UE@K&ZJ9q-q=1eB?+8G zOuU$F>nNx@w2Bp~xo+?HZbhD6OS<3Blckw)&LuzTNXm|usZYqEr~SO00Scwd~OM1`;rE)W07HOQgGm7GRbM*w-sN8{XT_D zKHDIo5GLSt(@k}^QkXo_K3Iq#O=(17^NDD+?kH`7PLjQhjg+`390UKS8!d-&v+fSA zt)j9a;-m6C+j!t}(A{aTL?r|Z9~7Fo_^=cF()JLupnxR4+Tj}Rr=~U$h3COvpQ(MT__FXc4p&`4v+vsCQ|yqe4tzGMl}g0NmW3t1dYGONMg*Rx zn5cm?GzaxOgavhiyFmX+#xk4*ptg`MR(D*0?qY_;U`OR%@`r%~@!xG`(BP;Cd1W z7jM0y^~_>%vVH$#1>)(M}HLizEccu7&8%Cp}BrT5K!wH z%?*#`CuqDc!~Mg#jHnQw%|L!LOy;ACAY%~q_@WKey>}Vc|CYI-Ipw{xYI>@*&#`(% z6@V;-toudbPh?-_l_tMX^D{-+R_**wOnNe+`h{d@>^?c@gn3JLOv{w}Q>*r^2i4JA znkj_Im?Tp zD+3zDvMuxS>;C&Su@Q+HlY~JY=Z7mYP%O@{7xcjuzsU-wRpe!PHsGY^+D?nm6fcK|A~MZrJ*w zh|$Cg4zC8rbHrwexgd7{(_H$Z6l)#c7z>6pP=b*nO0AnzlsodQo}jaqDZ*qKfpSz3 zd!+rdmrWU3e0xc@8XQE%{u!x>HfoI$YVZ>mw2Fb~gYvyO5hV>{AKiF}KW>!FfU`0_ zLx#QBghdmGLnC13HmrQ!k-s0X3DfR<0@QfPy$FBno@_4a;hNxX?rBieK%xSZqTO&) zYdtZcn6yQDoV_SFr77m2I;70VH2``Hys0GxU_Y$$?m%%ebJ=`F+e9;9~D4#_p=zgg38L>ZPK{$6(ncFynhf(ah zC(sV5){h4FXDDqNSD|gHG-v;wM7~zOBZLAW;~4U3b|DOMr8Yc?#&3(+6t=z;(^cTi zmm+$g7-OwHwE+H+TNtG87gn4>t1x*afML(N47%Y)<#v2V(Q%jn>fz+;40fLt-fWiX z{g?MWD=U8kU@|o)N0juKE>^S1;#MT@m`06)r9slTq`yRq5~|m{87ggcR(p4{W#lwb zg(JpDuGOP=)H(P-ht7!}fs`yhRT^>XM}|AaX-ogD{GL@Qved~h3oMx34Dvj} z%TD;`Xls@U@F5!lJKL?Pe!9_Xhj0GB$cKmCu)jaC7Q6^!ZSzuDy9do*)NNrwfos$3BWJQ-PEcYN z+z?7|f@n53udO#zdN|5mT^&7Ia~(?Ky2OUyQ{FDp>huK2-9(Naz3%1kU+`KXL+FHA zzZV!2Yc|uL4BSGfI!m5F#sl|o9xAD!B`gzo)1Y{4g+Bnz;7<%MjmwIAGEz? z>)K1<=CklRi>%roaMOuKZy_iE;;B^F0zdLSv?$a$pfF$3pk1tQu5SG>blRrgg!>LD zog6`(1TH#NtjZ2|txgxRo>elB$csI>~BU`Q=rT z^4L&Vp4V7i;?npQI(xST@q3B8Fy4rZs=ki78Fz101pO%No%f)BL%ydr!O8W*phhVu zZG&z#8D$rWN_6(4%cn!PNT2kOWH1r)5IF;Vxm~4Gm!=-KUlLRA#tgpReO%#?!-FgB zzr&nK>Omcz7!r`fm%f!G)93jH1vBB)EmxJEDk#LtF3gV#pGleT!n{Q&?8VbU|I+Yi zCp-S8L5`Wv>)PFim*#~y-za#mgc~I8or3i09^T2%?9gT5!k@!r0W0tAm8I8l(>X>) zBlmXE!!TK;cPYU3yn&+X`Hb;^=GaTzPx^�q@YG$DYs)%h4JRPO>s@ymrvZqJ^Py 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 F{vSHpW10W} literal 11416 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 Date: Wed, 3 Jun 2026 16:06:39 +0200 Subject: [PATCH 48/67] Add S2 Standard access link Added a link to ITEH for accessing the S2 Standard. --- website/docs/specs/specs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/specs/specs.md b/website/docs/specs/specs.md index ad5416e..6e52779 100644 --- a/website/docs/specs/specs.md +++ b/website/docs/specs/specs.md @@ -5,7 +5,7 @@ title: Overview # Overview of specifications ## The S2 Standard -The official standard (EN 50491-12-2) that has been approved by European Standardization bodies through CEN/CENELEC. To acquire access to the standard please refer to a national standardization body that is member of CEN/CENELEC. +The official standard (EN 50491-12-2) that has been approved by European Standardization bodies through CEN/CENELEC. To acquire access to the standard please refer to a national standardization body that is member of CEN/CENELEC or to [ITEH](https://standards.iteh.ai/catalog/standards/clc/6c89cfc7-4f3c-429e-9c75-62307b1572ab/en-50491-12-2-2022). ## S2-Json A json-schema definition that describes all S2 messages. From f9c93ad401e8681e6e24c3b20e2152d72c4cd661 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Fri, 5 Jun 2026 11:12:59 +0200 Subject: [PATCH 49/67] Added subnet check to LAN-LAN only REST operations --- .../discovery-pairing-authentication.md | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 996def5..cdf32f9 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -496,7 +496,17 @@ Where: | `D` | Binary data | The domain name of the HTTPS server, including subdomains, without protocol or trailing slashes (e.g. `pairing.s2.example.com`) | | `\|\|` | Function | Concatenation function | -## Getting endpoint information +## LAN-LAN only interactions + +The are several REST operations that are only to be implemented by LAN endpoints, and that can only be used by other LAN endpoints. They are described in this section. + +WAN endpoints **cannot** implement these operations. It is **recommended** that WAN endpoints respond with status code 404 when they receive requests for these operations. + +Since these operations are only intended for WAN endpoints within the same LAN, the pairing server must check if the requests originates from within the same LAN. Therefore the pairing server **must** check if the request originated from the same subnet. This functionality **must** be implemented in such a way that it works with both IPv4 and IPv6. When a request does not originate from the same subnet the server **must** respond with status code 401. + +> Note: There are some network configurations imaginable where it would be desirable to pair two LAN nodes that are not in the same subnet. In that case automatic discovery via DNS-SD will not work, but pairing by manually entering the pairing URL and pairing code can still be used in those cases. + +### Getting endpoint information > This section is only applicable for LAN-LAN pairing @@ -511,7 +521,7 @@ Before the HTTPS client can start interaction with the server, it must first sel Note that there is no authentication for these operations. It is assumed that these operations are only exposed within the LAN. -## Pre-pairing interaction +### Pre-pairing interaction > This section is only applicable for LAN-LAN pairing @@ -523,7 +533,7 @@ Note that there is no authentication for these operations. It is assumed that th Before sending signals the HTTPS client **must** have selected the version of the pairing API that will be used (see [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis)). -## Sending the prepare pairing signal +#### Sending the prepare pairing signal The client can send the prepare pairing signal to the server by sending an HTTPS POST request to the path `/preparePairing`. The client must perform the following checks before sending information: @@ -547,10 +557,11 @@ The server **must** perform the checks in the table below. For the checks with H | Does it recognize the `serverNodeId`? | 400 | `NodeNotFound` | | Are the endpoint and node ready for pairing? | 400 | `Other` | | Does the targeted node have a different role than the initiator node (i.e. you cannot pair two RM's or two CEM's)? | 400 | `InvalidCombinationOfRoles` | +| Does the request originate from inside the subnet? | 401 | n/a | If no checks fail the server **should** respond with HTTP status code 204. -## Cancelling the prepare pairing signal +#### Cancelling the prepare pairing signal If the client sent a prepare pairing signal the the server, and the end user has indicated in some way that it is no longer indented to pair with the node, it **should** send a cancel prepare pairing signal. It can do that by sending an HTTPS POST request to the path `/cancelPreparePairing`. The client must perform the following checks before sending information: @@ -565,9 +576,9 @@ The client **must** send the following information in the request. For full norm | `clientNodeId` | The node ID of the node at the client that the end user intents to pair with the node at the server | | `serverNodeId` | The node ID of the node at the server that the end user intents to pair with the node at the client | -The server **should** respond with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). +The server **should** respond with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). However, if the request originated from outside the subnet the server **must** respond with status 401. -## Long-polling +### Long-polling > This section is only applicable for LAN-LAN pairing @@ -589,14 +600,16 @@ The long-polling feature fulfills the following functionality: * Send the signal from the server to the client to initiate pairing for a particular node ID * Send an error message from the client to the server when pairing cannot be performed +Before the HTTPS client can start interaction with the server, it must first select a version of the API to use. See [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis). For full normative details see the OpenAPI specification files. + A client capable of long-polling **should** initiates long-polling when it encounters a endpoint through DNS-SD that indicates that is available for long-polling requests. When the endpoint represents zero nodes the client **cannot** attempt long-polling. When the endpoint advertisement itself, or only its long-polling indication disappears from DNS-SD the client **should** stop the long-polling process for that server. The client **must** also stop when it is no longer capable of pairing. The server **must** always respond within 25 seconds after receiving the request. The client **must** use a request time-out of at least 30 seconds. -> TODO: Move the OpenAPI version selection process to its own section so we don't have to explain it every time - The client starts the process by doing a POST request to the `/waitForPairing` path. For full normative details see the OpenAPI specification files. The request body contains a list of objects. The client **must** always provide an object for each node ID it represents. The items in the list have a mandatory property `clientNodeId` and optional parameters `clientNodeDescription`, `clientEndpointDescription`. The client should only provide values for these properties when requested by the server. The object also contains the optional property `errorMessage`, which only should be used when an error has occurred before pairing. +The server **must** check if the requests originates from within the same subnet. If it does not, it **must** reply with HTTP status code 401. + When the server wants the client to immediately do a new request, it responds with status code 204. When it wants the client to do something, it responds with status 200 and a response body containing a list. This list contains an object only for node IDs represented by the client, that the server wants to do something with. This object contains the mandatory properties `clientNodeId` and `action`. The `action` property is an enumeration indicating an action the server wants to execute for a specific node. The possible action values are `sendNodeDescription`, `preparePairing`, `cancelPreparePairing` and `requestPairing`. The table below indicates how the client should respond to the requests of the server. Note that the server could send multiple actions (for different node IDs) in the same response. The server **cannot** provide multiple objects for the same node ID in one response. From ee2be76ea8d00dd865cac37b193a5b426892ef85 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Mon, 8 Jun 2026 13:30:02 +0200 Subject: [PATCH 50/67] Update website/s2-connect/discovery-pairing-authentication.md Co-authored-by: Jorrit Nutma --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index cdf32f9..3a82bd7 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -502,7 +502,7 @@ The are several REST operations that are only to be implemented by LAN endpoints WAN endpoints **cannot** implement these operations. It is **recommended** that WAN endpoints respond with status code 404 when they receive requests for these operations. -Since these operations are only intended for WAN endpoints within the same LAN, the pairing server must check if the requests originates from within the same LAN. Therefore the pairing server **must** check if the request originated from the same subnet. This functionality **must** be implemented in such a way that it works with both IPv4 and IPv6. When a request does not originate from the same subnet the server **must** respond with status code 401. +Since these operations are only intended for endpoints within the same LAN, the pairing server must check if the requests originates from within the same LAN. Therefore the pairing server **must** check if the request originated from the same subnet. This functionality **must** be implemented in such a way that it works with both IPv4 and IPv6. When a request does not originate from the same subnet the server **must** respond with status code 401. > Note: There are some network configurations imaginable where it would be desirable to pair two LAN nodes that are not in the same subnet. In that case automatic discovery via DNS-SD will not work, but pairing by manually entering the pairing URL and pairing code can still be used in those cases. From 93fb3c36d56df5d6f081c95e44bdbd0776d9871c Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Mon, 8 Jun 2026 15:36:28 +0200 Subject: [PATCH 51/67] Introduced the term Pairing URL and specified it more clearly --- .../discovery-pairing-authentication.md | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 3a82bd7..67346ee 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -100,6 +100,7 @@ This specification uses the concepts that are defined below. | Pairing endpoint registry | The central registry that keeps track of publicly available pairing servers. | | Pairing server | The endpoint that behaves as the HTTPS server when pairing with a node. | | Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and a node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Pairing URL | The URL of the pairing API on an endpoint (see [Pairing URL](#pairing-url)) | | Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | | Session | A stateful exchange of S2 messages between two S2 nodes | | User interface | A user interface through which an end user can interact with a node. Interactions between the end user and the user interface must be secure, but this is out of scope for this specification. Examples of a user interface are a web interface, an app or a physical interface (HMI) on a device. | @@ -182,7 +183,7 @@ One of the main technologies the process relies on is HTTPS REST. All interactio ### Discovery -The first step is finding the responder node from the initiator node. In principle this is done based on the URL of the responder node. However, to improve user experience, two systems exist to find this URL in a more user friendly manner. For more details see [Discovery](#discovery). +The first step is finding the responder node from the initiator node. In principle this is done based on the pairing URL of the responder node. However, to improve user experience, two systems exist to find the pairing URL in a more user friendly manner. For more details see [Discovery](#discovery). * If the responder node is deployed in the WAN, the end user can find the endpoint through the pairing endpoint registry. This would result in a list of vendors that offer nodes. * If both nodes are deployed in the LAN however, the responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. @@ -252,7 +253,7 @@ The minor version is increased when backwards compatible changes are made. Be aw The major version is increased when non-backwards compatible changes are made. -The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTPS server and HTTPS clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must server all version on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as port of the URL (e.g. `["v1", "v2"]`). +The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTPS server and HTTPS clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must server all version on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as part of the URL (e.g. `["v1", "v2"]`). ## Versioning of JSON Schema files JSON Schema uses its own versioning scheme, which is based on an `major.minor.patch` scheme. When negotiating the S2 JSON version number, the exact version string **must** be used (e.g. `v1.0.0`). @@ -264,6 +265,8 @@ For **WAN** deployed endpoints, the URL **must** be based on a DNS domain name. For **LAN** deployed endpoints, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instance within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. +Also see [Pairing URL](#pairing-url). + ## Selecting the version of REST APIs As explained in the section [Versioning of OpenAPI files](#version) the pairing server, the session initiation server and the WAN pairing endpoint registry can implement multiple versions of the API specification in parallel. As a result, the client **must** always first determine which version of the API it will use, before it can start interacting with the API. @@ -333,9 +336,16 @@ For each pairing attempt, one endpoint must be the HTTPS server, while the other A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. A node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). +## Pairing URL +The start of each pairing related interaction is the initiator node contacting the responder node via the *pairing URL*. Although discovery provides an option to retrieve this URL in a user friendly manner, entering the pairing URL manually **should** always be an option. Therefore, every responder node **should** display its pairing URL somewhere in its UI, and every initiator node **should** have an option to pair based on a pairing URL which is manually entered by the end user. + +The pairing URL is the base URL of the pairing API of an endpoint. It **must** include the protocol (`https://`), it **must not** include the version of the API, but it **must** include a trailing slash (e.g. `https://hostname.local/pairing/`). + +For information about the domain name used in the URL see [Addressing endpoints](#addressing-endpoints). + ## Discovery -In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing endpoint URL of the other node. In other words, the discovery process is a way to provide a node with the URL of another node which is needed to start the pairing process. Alternatively, it **must** always be possible to initiate the pairing by manually providing the URL by the end user. +In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing URL of the other node. In other words, the discovery process is a way to provide a node with the pairing URL of another node which is needed to start the pairing process. Alternatively, it should always be possible to initiate the pairing by manually providing the URL by the end user (see [Pairing URL](#pairing-url)). There are two mechanisms for discovery: For discovering WAN endpoints there is a central online registry. For discovering endpoints within the same LAN, DNS-SD is used. @@ -343,7 +353,7 @@ There are two mechanisms for discovery: For discovering WAN endpoints there is a > Note: At this point the registry is specified, but not yet publicly available -The purpose of the registry is to facilitate a more user friendly way to determine the URL of the WAN pairing endpoint. Providers of an S2 Connect WAN pairing endpoint can register their endpoint at the registry. The user interface of a CEM or RM could show a list of relevant endpoints to the user (e.g., in a list or drop down menu) with details that would be easily recognizable to the end user (e.g., name and icon). By querying the registry, the user interface can always show an up-to-date list of endpoints. The registry contains filtering functionality to filter endpoints that are relevant in the context. +The purpose of the registry is to facilitate a more user friendly way to determine the pairing URL of the WAN endpoint. Providers of an S2 Connect WAN pairing endpoint can register their endpoint at the registry. The user interface of a CEM or RM could show a list of relevant endpoints to the user (e.g., in a list or drop down menu) with details that would be easily recognizable to the end user (e.g., name and icon). By querying the registry, the user interface can always show an up-to-date list of endpoints. The registry contains filtering functionality to filter endpoints that are relevant in the context. The registry uses the same version negotiation mechanism as the other S2 Connect OpenAPI files. Refer to [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis) for information on how clients can select the API version to use. @@ -357,7 +367,7 @@ The registry contains the following information for each endpoint. For full norm | `icon32` | 32 by 32 pixel- icon of the endpoint | | `icon128` | 128 by 128 pixels icon of the endpoint | | `icon512` | 512 by 512 pixels icon of the endpoint | -| `pairingApiUrl` | The URL of the pairing API of the endpoint | +| `pairingUrl` | The pairing URL of the endpoint | | `regions` | Array of regions in which this endpoint operates, as defined by the ISO 3166-1 alpha-2 country code | | `status` | Status of the endpoint, can either be `testing` or `public` | | `cem` | Boolean indicating if the endpoint represents CEM nodes | @@ -403,7 +413,7 @@ S2 uses the following key-value pairs in the TXT record when registering for ser | `txtver` | M | Version of this specification of usage of the TXT record. **Must** be the literal string value `1` for this version | | `e_name` | O | The name of this endpoint (identical to the `name` property in the `EndpointDescription` object as defined in de OpenAPI specification) | | `e_logoUrl` | O | The logoUrl of this endpoint (identical to the `logoUrl` property in the `EndpointDescription` object as defined in de OpenAPI specification) | -| `pairingUrl` | O | The base URL of the pairing API of this endpoint, excluding the version name but including the last slash (e.g. `https://hostname.local/pairing/`). If no value is provided, a `longpollingUrl` **must** be provided. +| `pairingUrl` | O | The base URL of the pairing API of this endpoint (see [Pairing URL](#pairing-url)). If no value is provided, a `longpollingUrl` **must** be provided. | `longpollingUrl` | O | The base URL of the pairing API of this endpoint on which the longpolling feature is implemented. The URL should be provided excluding the version name but including the last slash (e.g. `https://hostname.local/pairing/`). Only needs to be provided when longpolling is supported. Can only be provided if the value for `deployment` is equal to `LAN`. > Note: It is mandatory to provide a value for at least one of the properties `pairingUrl` and `longpollingUrl`. Providing both is also possible. @@ -510,7 +520,7 @@ Since these operations are only intended for endpoints within the same LAN, the > This section is only applicable for LAN-LAN pairing -Once a LAN endpoint has discovered a LAN pairing endpoint (through DNS-SD or by the end user manually entering the URL), it still knows very little about the endpoint. There are two REST operations that allow an HTTPS client to query information of a server endpoint: performing a GET on `/endpoint` and performing a GET on `/nodes`. +Once a LAN endpoint has discovered a LAN pairing endpoint (through DNS-SD), it still knows very little about the endpoint. There are two REST operations that allow an HTTPS client to query information of a server endpoint: performing a GET on `/endpoint` and performing a GET on `/nodes`. These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the initiator node is the HTTPS client and the responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). @@ -692,7 +702,7 @@ Note over Client, Server: Pairing finalized Before two node can be paired, the following preconditions must be met. 1. The HTTPS server and the HTTPS client can only start with a pairing request when they are fully initialized and have all the details of the nodes it represents available. -2. The HTTPS client must have the base URL of the pairing API (e.g. `https://hostname.local/pairing/`) +2. The HTTPS client must have the pairing URL of the endpoint (see [Pairing URL](#pairing-url)) 3. The HTTPS client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) 4. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. @@ -826,7 +836,7 @@ The server responds with two pieces of information: | Information | Description | | --- | --- | -| `initiateConnectionUrl` | The base URL for the connection process (does not include the version number) | +| `initiateSessionUrl` | The base URL for the connection process (does not include the version number) | | `accessToken` | The access token that was generated for this node | If the response is understood and properly formatted, the HTTPS client **should** proceed to the next step. Otherwise the HTTPS client **must** stop the pairing attempt. It **must** attempt to inform the HTTPS server of this by doing an HTTPS request to `finalizePairing` where the value of `success` must be `false`. @@ -841,7 +851,7 @@ In this case the pairing server will become the communication client. Once the p | Information | Description | | --- | --- | | `serverHmacChallengeResponse` | The response for the challenge response process | -| `initiateConnectionUrl` | The base URI for the connection process (does not include the version number) | +| `initiateSessionUrl` | The base URI for the connection process (does not include the version number) | | `accessToken` | The access token that was generated for this node | | `certificateFingerprint` | A map with the fingerprint of the CA (root) certificate. The key of the map is the name of the hashing algorithm used to generate the fingerprint, the value is the fingerprint itself. The key `SHA256` must always be provided. | From e1ccdc1513dceaa7bc3a61af7ad1cf2e90bb7a0f Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 9 Jun 2026 10:20:37 +0200 Subject: [PATCH 52/67] Update website/s2-connect/discovery-pairing-authentication.md Fixed typo Co-authored-by: Jorrit Nutma --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 67346ee..96758c1 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -263,7 +263,7 @@ The URL of the pairing and session initiation API are used in the discovery proc For **WAN** deployed endpoints, the URL **must** be based on a DNS domain name. -For **LAN** deployed endpoints, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instance within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. +For **LAN** deployed endpoints, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instances within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. Also see [Pairing URL](#pairing-url). From 83bafaeda3c5c984e81e85658a379e0cce0c53bd Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Tue, 9 Jun 2026 14:35:43 +0200 Subject: [PATCH 53/67] Pairing via URL is a mandatory option --- website/s2-connect/discovery-pairing-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 96758c1..cc69fe9 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -337,7 +337,7 @@ For each pairing attempt, one endpoint must be the HTTPS server, while the other A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. A node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). ## Pairing URL -The start of each pairing related interaction is the initiator node contacting the responder node via the *pairing URL*. Although discovery provides an option to retrieve this URL in a user friendly manner, entering the pairing URL manually **should** always be an option. Therefore, every responder node **should** display its pairing URL somewhere in its UI, and every initiator node **should** have an option to pair based on a pairing URL which is manually entered by the end user. +The start of each pairing related interaction is the initiator node contacting the responder node via the *pairing URL*. Although discovery provides an option to retrieve this URL in a user friendly manner, entering the pairing URL manually **must** always be an option. Therefore, every responder node **should** display its pairing URL somewhere (e.g. in its UI), and every initiator node **should** have an option to pair based on a pairing URL which is manually entered by the end user. The pairing URL is the base URL of the pairing API of an endpoint. It **must** include the protocol (`https://`), it **must not** include the version of the API, but it **must** include a trailing slash (e.g. `https://hostname.local/pairing/`). From 05b4e07112d2a6477e0fb76854b2a8c5b0ddfd2d Mon Sep 17 00:00:00 2001 From: Ewoud Werkman Date: Wed, 24 Jun 2026 09:05:48 +0200 Subject: [PATCH 54/67] Review Ewoud --- .../discovery-pairing-authentication.md | 154 ++++++++++-------- .../powerpoint_img_source.pptx | Bin 164772 -> 203353 bytes 2 files changed, 83 insertions(+), 71 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index cc69fe9..3929f7f 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -45,9 +45,11 @@ This version of this specification is based on the following versions of the und # Background (informative) ## Context -S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other over IP networks. S2 Connect builds upon two other projects: +S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other over IP networks. The general architecture is described in the S2 architecture standard introducing CEM and RM functions are defined in EN 50491-12-1:2018, in which also the S2 interface is defined between CEM and RM. -* The **S2 Standard** (formally known as EN50491-12-2) describes the data models and interactions for S2 based communication +S2 Connect builds upon two other projects: + +* The **S2 Data model standard** (formally known as EN50491-12-2:2022) describes the data models and interactions for S2 based communication * The **S2 JSON** project formalizes the data models of the S2 standard into JSON Schemas for a messages-based interaction between CEM and RM Both the S2 Standard and S2 JSON can be used independently from S2 Connect. @@ -76,7 +78,7 @@ Non-repudiation is explicitly not guaranteed by this protocol. The entire trust model of S2 Connect is based on the fact that there is already a trust relation between the end user and the CEM and RM. If the CEM and RM do not use adequate security mechanisms, it might be possible to attack the system. ->> "Why doesn't S2 Connect use oAuth?" is a common question. oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. We have identified a way to make it work but since it is such non-typical way, we choose not to use oAuth 2.0. For more details see [here](why-not-oauth.md). +>> "Why doesn't S2 Connect use oAuth?" is a common question. oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. There is a way to make it work, but since it is such a non-typical way the decision was made to not use oAuth 2.0 for pairing. For more details see [here](why-not-oauth.md). # Terms and definitions (normative) @@ -90,18 +92,18 @@ This specification uses the concepts that are defined below. | End user | A person or entity that manages nodes. For the purpose of this specification it is assumed that there is already a trust relationship in place between this person and the nodes. This means that the way the trust relationship has been established is out of scope for this specification. | | End user environment | A restricted area within an application that contains all the nodes that belong to the end user. Also see [Deployment of nodes](#deployment-of-nodes). | | Endpoint | A service which can handle pairing requests or initiates pairing requests itself. An endpoint can represent one node, but could also represent many. | -| Initiator node | The node that takes the initiative to pair with a responder node. This is typically the node from which the user initiates the pairing process. It is the counterpart of the responder node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | +| Initiator node | The node that takes the initiative to pair with a Responder node. This is typically the node from which the user initiates the pairing process. It is the counterpart of the Responder node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | | Node | Refers to an instance of either a CEM or a RM as defined in EN 50491-12-1 and implementing this specification. S2 communication between two nodes can only be established if one of the nodes is a CEM and the other a RM. These nodes must also have the same end user. | | Node ID | A globally unique identifier for a node in the UUID format. | | Node ID alias | A short identifier for a node, which is unique in the context of a single endpoint. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Pairing attempt | The process of pairing two nodes. The process can be completed successfully or unsuccessfully. | | Pairing client | The endpoint that behaves as the HTTPS client when pairing with a node. | -| Pairing code | The pairing code is the string of characters the end user has to copy from the responder node user interface to the initiator node user interface, in order to pair the two nodes. The pairing code consists of a pairing token and if required a node ID alias. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Pairing code | The pairing code is the string of characters the end user has to copy from the Responder node user interface to the Initiator node user interface, in order to pair the two nodes. The pairing code consists of a pairing token and if required a node ID alias. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Pairing endpoint registry | The central registry that keeps track of publicly available pairing servers. | | Pairing server | The endpoint that behaves as the HTTPS server when pairing with a node. | | Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and a node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | | Pairing URL | The URL of the pairing API on an endpoint (see [Pairing URL](#pairing-url)) | -| Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | +| Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the Initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | | Session | A stateful exchange of S2 messages between two S2 nodes | | User interface | A user interface through which an end user can interact with a node. Interactions between the end user and the user interface must be secure, but this is out of scope for this specification. Examples of a user interface are a web interface, an app or a physical interface (HMI) on a device. | @@ -115,8 +117,8 @@ This specification is concerned with connecting an instance of a CEM with an ins Nodes can be deployed locally within the LAN, or somewhere on a server in the WAN. Although their deployment doesn't significantly affect the working of these nodes, there are some key differences between these types of deployment. -* **WAN** nodes are typically part of a large application that run on many servers and/or on some kind of cloud computing platform. A single application usually serves many users. Each user could have one or multiple nodes. This could for example be a cloud-based energy management system that can connect to many devices. It could also be a cloud environment of a device manufacturer that hosts the RM instances in the cloud. An end user could own multiple devices from this manufacturer, thus the application could host multiple RM instances for this particular user. We call a group of nodes that a single user can manage within one application an *end user environment*. It is also possible that an end user environment contains both CEM and RM instances. The user interface is typically a web interface or a smartphone app. -* **LAN** nodes are typically part of an application that runs on an embedded computer device somewhere in the building. Such a device could be a physical energy management system, an energy flexible device such as a home battery, heat pump or EV charger, or a gateway device which connects to an energy flexible device through some kind of protocol. Often an application will only host a single node, but it is also possible that an application hosts multiple nodes. A device could function completely on its own, but it could also be connected to an internet based application of the manufacturer. The user interface could be a physical human-machine interface on the device, but also be a smartphone app that connects directly to the device (e.g. via Bluetooth), or a smartphone app or web interface that connects to an internet based application of the manufacturer. For energy flexible devices, it is assumed that they could also have no user interface at all, or that they are very constrained when it comes to computing power. It is assumed that a CEM always has a user interface. +* **WAN** nodes are typically part of a large application that run on many servers and/or on some kind of cloud computing platform. A single application usually serves many users. Each user could have one or multiple nodes. This could for example be a cloud-based energy management system that can connect to many devices. It could also be a cloud environment of a device manufacturer that hosts the RM instances in the cloud. An end user could own multiple devices from this manufacturer, thus the application could host multiple RM instances for this particular user. A group of nodes that a single user can manage within one application is called an *end user environment*. It is also possible that an end user environment contains both CEM and RM instances. The user interface is typically a web interface or a smartphone app. +* **LAN** nodes are typically part of an application that runs on an embedded computer device somewhere in the building. Such a device could be a physical energy management system, an energy flexible device such as a home battery, heat pump or EV charger, or a gateway device which connects to an energy flexible device through some kind of protocol. Often an application will only host a single node, but it is also possible that an application hosts multiple nodes. A device could function completely on its own, but it could also be connected to an internet based application of the manufacturer. The user interface could be a physical human-machine interface on the device, but also be a smartphone app that connects directly to the device (e.g. via Bluetooth), or a smartphone app or web interface that connects to an internet based application of the manufacturer. For energy flexible devices, it is assumed that they could also have no user interface at all, or that they are very constrained when it comes to computing power or memory. It is assumed that a CEM always has a user interface. ![Deployment_options](@site/static/img/communication-layer/deployment_options.png) @@ -124,25 +126,27 @@ There are three types of S2 connections between nodes possible: * **WAN-WAN**: A connection between two nodes deployed in a WAN. Connecting between them is straightforward and can be done based on URLs, based on DNS domain names. It is possible to rely on TLS certificates that can be validated thanks to a public key infrastructure. * **WAN-LAN**: A connection between a LAN deployed node a WAN deployed node. Since there is almost always a firewall and/or NAT between these two, it is assumed that it is only possible to set up a connection from the LAN to the WAN; not the other way around. Connecting from the LAN node to the WAN node can be done based on a URL, and common TLS certificates can be used thanks to public key infrastructure. -* **LAN-LAN**: A connection between two LAN deployed nodes. It is assumed that in this situation we cannot rely an internet connection, making it impossible to rely on a public key infrastructure for TLS certificates. That is why for this type of connection self-signed TLS certificates are used. Connections are made based on hostnames that are resolved to IP-addresses using Multicast DNS (mDNS), since IP-addresses are not guaranteed to be stable. Discovering another node could be done using DNS Service Discovery (DNS-SD). +* **LAN-LAN**: A connection between two LAN deployed nodes. It is assumed that in this situation an internet connection might not be available, making it impossible to rely on a public key infrastructure for TLS certificates. That is why for this type of connection self-signed TLS certificates are used. Connections are made based on hostnames that are resolved to IP-addresses using Multicast DNS (mDNS), since IP-addresses are not guaranteed to be stable. Discovering another node could be done using DNS Service Discovery (DNS-SD). ## Pairing and unpairing from the perspective of the end user -The end user can take the initiative to *pair* a single CEM instance with a single RM instance. This process has to be started with one of the nodes. Which node this is depends on the deployment and implementation decisions of the node, but ideally it could be either one. The node however needs to have a user interface. We'll call the node that user uses to start the pairing process the *initiator*. We'll call the other node the *responder*. +The end user can take the initiative to *pair* a single CEM instance with a single RM instance. This process has to be started with one of the nodes. Which node this is depends on the deployment and implementation decisions of the node, but ideally it could be either one. The node however needs to have a user interface. The node that a user uses to start the pairing process is called the **Initiator node**. The other node receiving the pairing request is called the **Responder node**. -The first step of pairing is establishing a connection from the initiator node to the responder node. This can be done in several ways: -* Enter the responder node address manually at the initiator node. -* If the responder node is deployed in the WAN, the URL could be retrieved through a registry. The end user would have to select the type of node from a list of known node services in its region. -* If both nodes are deployed in the LAN however, nodes can be automatically be detected. The end user would have to select the node from a list of automatically discovered nodes. +The first step of pairing is establishing a connection from the Initiator node to the Responder node. This can be done in several ways: +* Enter the Responder node address manually at the Initiator node. +* If the Responder node is deployed in the WAN, the URL could be retrieved through a registry. The end user would have to select the type of node from a list of known node services in its region. +* If both nodes are deployed in the LAN however, nodes can be automatically be discovered. The end user would have to select the node from a list of automatically discovered nodes. -The second step is entering the pairing code of the responder node. This is a means for the end user to confirm that these two nodes are allowed to send control signals through S2 to each other. The pairing code can be obtained from the responder node. The pairing code is a (seemingly) random string of characters. This pairing code is typically displayed somewhere in the user interface of the other node. We recommend to use a dynamic token which expires after 5 minutes. However, if the node has a physical presence in the building and doesn't have user interface, there is also the option to have a static pairing code which can be printed on the device. +The second step is entering the pairing code of the Responder node. This is a means for the end user to confirm that these two nodes are allowed to send control signals to each other. The pairing code can be obtained from the Responder node. The pairing code is a (seemingly) random string of characters. This pairing code is typically displayed somewhere in the user interface of the Responder node. It is recommended to use a dynamic token which expires after 5 minutes. However, if the Responder node has a physical presence in the building and doesn't have user interface, there is also the option to have a static pairing code which can be printed on the device. -Optionally, the initiator node can send a signal to the responder node to indicate that the end user has started the pairing process and has selected the responder node. This could trigger the user interface of the responder node to proactively show the pairing code (e.g. through a pop-up) to improve the user experience. +Optionally, the Initiator node can send a signal to the Responder node to indicate that the end user has started the pairing process and has selected the Responder node. This could trigger the user interface of the Responder node to proactively show the pairing code (e.g. through a pop-up) to improve the user experience. -Once the pairing code is known to the initiator node, the pairing process is started. It is established that both nodes are compatible and it is verified that the entered pairing code is correct. Pairing could either fail or succeed. +Once the pairing code is known to the Initiator node, the pairing process is started. It is established that both nodes are compatible and it is verified that the entered pairing code is correct. Pairing could either fail or succeed. If pairing is performed successfully, the CEM and RM instances should establish a session with each other and communicate through the exchange of S2 messages. If the connection is interrupted, the instances will automatically try to reestablish the connection. -Once a CEM is paired, the user has to possibility to command either of the nodes to *unpair*. After unpairing the CEM and RM instances can no longer communicate through S2 (unless the end user pairs them again). +Once a CEM is paired, the user has the possibility to command either of the nodes to *unpair*. After unpairing the CEM and RM instances can no longer communicate through S2 (unless the end user pairs them again). + +The pairing process is depicted in the figure below: ![Pairing_process_user](@site/static/img/communication-layer/pairing_process_user.png) @@ -169,46 +173,46 @@ iui->e: Pairing result (success or failure) ## The node and the endpoint -Within this protocol we make a clear distinction between two types of identities: the one of the *endpoint* and the one of the *node* itself. +Within this protocol a clear distinction is made between two types of identities: the one of the **Endpoint** and the one of the **Node** itself. -A node is an instance of a CEM or a RM. It is either a specific energy management system or service, or the representative of a physical energy flexible device, such as a heat pump or a home battery. It is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. +A **Node** is an instance of a CEM or a RM. It is either a specific energy management system or service, or the representative of a physical energy flexible device, such as a heat pump or a home battery. It is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. -The endpoint is basically the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. +The **Endpoint** is the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. ## Used technology for pairing and communication -An S2 connection basically consists of four steps: discovery, pairing, communication and unpairing. For these steps different types of technology are used. +An S2 connection consists of four steps: discovery, pairing, communication and unpairing. For these steps different types of technology are used. One of the main technologies the process relies on is HTTPS REST. All interactions based on HTTPS are formally described in OpenAPI specification files. [OpenAPI](https://swagger.io/specification/) is a formal language for specifying HTTP(S) based API's. It can be used to generate reference documentation for developers, as well as stub code for many programming languages. ### Discovery -The first step is finding the responder node from the initiator node. In principle this is done based on the pairing URL of the responder node. However, to improve user experience, two systems exist to find the pairing URL in a more user friendly manner. For more details see [Discovery](#discovery). +The first step is finding the Responder node from the Initiator node. In principle this is done based on the pairing URL of the Responder node. However, to improve user experience, two systems exist to find the pairing URL in a more user friendly manner. For more details see [Discovery](#discovery). -* If the responder node is deployed in the WAN, the end user can find the endpoint through the pairing endpoint registry. This would result in a list of vendors that offer nodes. -* If both nodes are deployed in the LAN however, the responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. +* If the Responder node is deployed in the WAN, the end user can find the endpoint through the pairing endpoint registry. This would result in a list of vendors that offer nodes. +* If both nodes are deployed in the LAN however, the Responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. ### Pairing The pairing process itself is completely based on HTTPS REST. One node behaves as the HTTPS server, and the other as the HTTPS client. This process is described in an OpenAPI file. The process consists of multiple steps. If the pairing process is completed successfully, the nodes will agree on an access token. This token is used to initiate communication or to unpair. -We'll refer to the endpoint that behaves as the HTTPS server during the pairing process as the *pairing server*, and the client as the *pairing client*. +The endpoint that behaves as the HTTPS server during the pairing process is defined as the *pairing server*, and the client is defined as the *pairing client*. Pairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) ### Communication -Communication is setting up the actual session, where S2 messages are being exchanged. +A communication process is used to set up the actual session that enables the exchange of S2 messages. -The process always starts with HTTPS based communication, but then is handed over to a protocol which supports a two-way messages based communication channel. Currently the only protocol that is being used is WebSockets, but there are plans to add other options in the future. The HTTPS interface is also specified in an OpenAPI file, together with the unpairing process. +The process always starts with HTTPS based communication, but then is handed over to a protocol which supports a two-way messages based communication channel. Currently the main protocol that is being used is WebSockets, but there are plans to add other options in the future, such as MQTT. This communication setup process using a HTTPS interface is also specified in an OpenAPI file, together with the unpairing process. -We'll refer to the endpoint that behaves as the HTTPS server during the communication process as the *communication server*, and the client as the *communication client*. +The endpoint that behaves as the HTTPS server during the communication process is called the **Communication server**, and the client is called the **Communication client**. It should be noted that pairing and communication are two separate HTTPS interfaces, that don't have to be used in the same way. It could be that a node is an pairing client, but then becomes a communication server. This depends on the deployment of the nodes (see [Pairing details for different deployments](#pairing-details-for-different-deployments)). Communication interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) -After the HTTPS interaction a WebSocket is established (other transport protocols will be added in the future). The communication server is always the WebSocket server. This server must use the same TLS certificate as the HTTPS server. +After the HTTPS interaction communication with the selected communication protocol is established (WebSocket, other transport protocols such as MQTT will be added in the future). For WebSocket communication, the communication server is always the WebSocket server. This server must use the same TLS certificate as the HTTPS server. ### Unpairing @@ -219,21 +223,23 @@ Unpairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployme ## Pairing details for different deployments -As explained, the pairing process is based on HTTPS REST calls. That means that for every pairing attempt, one node behaves as the HTTPS server, and one node behaves as the HTTPS client. The logical solution would be to make the initiator node the HTTPS client and the responder node the HTTPS server. After all, it is the HTTPS client that takes the initiative to contact the HTTPS server. The HTTPS server cannot take the initiative to contact the HTTPS client. +As explained, the pairing process is based on HTTPS REST calls. That means that for every pairing attempt, one node behaves as the HTTPS server, and one node behaves as the HTTPS client. The logical solution would be to make the Initiator node the HTTPS client and the Responder node the HTTPS server. After all, it is the HTTPS client that takes the initiative to contact the HTTPS server. The HTTPS server cannot take the initiative to contact the HTTPS client. -The objective is to have all nodes be able to be the initiator node, as well as the responder node. This is necessary to provide a consistent user experience. The end user might not be aware which node is deployed in the LAN or in the WAN, and then it might be confusing that, for example, his energy management system both provides pairing codes and asks for pairing codes. +The objective is to have all nodes be able to be the Initiator node, as well as the Responder node. This is necessary to provide a consistent user experience. The end user might not be aware which node is deployed in the LAN or in the WAN, and then it might be confusing that, for example, his energy management system both provides pairing codes and asks for pairing codes. -If every node must be able to be the initiator node in certain situations, and the responder node in other situations, and the easiest solution is to implement the initiator as HTTPS client and the responder as HTTPS server, you might come to the conclusion that every node needs to be able to behave both as an HTTPS server and as an HTTPS client. +If every node must be able to be the Initiator node in certain situations, and the Responder node in other situations, and the easiest solution is to implement the initiator as HTTPS client and the responder as HTTPS server, you might come to the conclusion that every node needs to be able to behave both as an HTTPS server and as an HTTPS client. There are however two situations where this is not possible: -* **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that approaching a LAN HTTPS server from a WAN client is not possible. This specifications offers two approaches to this problem: - * Accept this limitation and not allow the WAN node to be the initiator node. Pairing can only be performed when the LAN node is the initiator node and the WAN node is the responder node. Special care must be taken to explain this to the end user. +* **WAN Initiator node and LAN Responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that approaching a LAN HTTPS server from a WAN client is not possible. This specifications offers two approaches to this problem: + * Accept this limitation and not allow the WAN node to be the Initiator node. Pairing can only be performed when the LAN node is the Initiator node and the WAN node is the Responder node. Special care must be taken to explain this to the end user. * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTPS server in the cloud, even though the node itself is in the LAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients and must not be used by LAN clients. There must always be a method for purely LAN based pairing. -* **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTPS server is far more memory intensive than an HTTPS client, there is an option to implement a LAN RM instance purely as an HTTPS client. A long-polling mechanism is available to indicate to the HTTPS server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTPS server. In other words: in this specific situation the initiator node behaves as the HTTPS server, and the responder node only has to be an HTTPS client. +* **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTPS server is far more memory intensive than an HTTPS client, there is an option to implement a LAN RM instance purely as an HTTPS client. A long-polling mechanism is available to indicate to the HTTPS server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTPS server. In other words: in this specific situation the Initiator node behaves as the HTTPS server, and the Responder node only has to be an HTTPS client. ![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) +>>>> **Ewoud**: Plaatje opsplitsen want dit is de veel pijltjes. Alternatief voor WAN-LAN heb ik gemaakt in powerpoitn + # Formal specification and versioning (normative) @@ -249,11 +255,11 @@ This document serves as an overall specification of the S2 Connect protocol. How ## Versioning of OpenAPI files The pairing API, the session initiation API and the WAN pairing endpoint registry API are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. All S2 Connect OpenAPI files share the same version number. -The minor version is increased when backwards compatible changes are made. Be aware that we consider adding items to certain lists of enums (e.g. the list of supported hash functions) backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. +The minor version is increased when backwards compatible changes are made. Be aware that adding items to certain lists of enums (e.g. the list of supported hash functions) is considered backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. The major version is increased when non-backwards compatible changes are made. -The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTPS server and HTTPS clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must server all version on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as part of the URL (e.g. `["v1", "v2"]`). +The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTPS server and HTTPS clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must serve all versions on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as part of the URL (e.g. `["v1", "v2"]`). ## Versioning of JSON Schema files JSON Schema uses its own versioning scheme, which is based on an `major.minor.patch` scheme. When negotiating the S2 JSON version number, the exact version string **must** be used (e.g. `v1.0.0`). @@ -263,7 +269,7 @@ The URL of the pairing and session initiation API are used in the discovery proc For **WAN** deployed endpoints, the URL **must** be based on a DNS domain name. -For **LAN** deployed endpoints, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instances within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. +For **LAN** deployed endpoints, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instances within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. Also see [Pairing URL](#pairing-url). @@ -274,6 +280,8 @@ The image below depicts the interactions between client and server for the proce ![image](@site/static/img/communication-layer/api_version_selection.png) +>>>> **Ewoud**: GET / is een beetje verwarrend als de URL bijv. https://device.local/s2c/ is. Dus GET / vervangen door GET root of supplied URL? +
Image generated using the following PlantUML code: @@ -334,10 +342,10 @@ For each pairing attempt, one endpoint must be the HTTPS server, while the other > A LAN deployed RM implementation can choose if it implements the HTTPS server, or that it implements the HTTPS client and uses long-polling. This feature exists to accommodate RM implementations with constrained hardware. -A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. A node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). +A CEM can be paired with multiple RMs at the same time. A RM can only be paired with one CEM at a time. A node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). ## Pairing URL -The start of each pairing related interaction is the initiator node contacting the responder node via the *pairing URL*. Although discovery provides an option to retrieve this URL in a user friendly manner, entering the pairing URL manually **must** always be an option. Therefore, every responder node **should** display its pairing URL somewhere (e.g. in its UI), and every initiator node **should** have an option to pair based on a pairing URL which is manually entered by the end user. +The start of each pairing related interaction is the Initiator node contacting the Responder node via the *pairing URL*. Although discovery provides an option to retrieve this URL in a user friendly manner, entering the pairing URL manually **must** always be an option. Therefore, every Responder node **should** display its pairing URL somewhere (e.g. in its UI), and every Initiator node **should** have an option to pair based on a pairing URL which is manually entered by the end user. The pairing URL is the base URL of the pairing API of an endpoint. It **must** include the protocol (`https://`), it **must not** include the version of the API, but it **must** include a trailing slash (e.g. `https://hostname.local/pairing/`). @@ -382,11 +390,13 @@ In order to filter out the relevant endpoint records the API supports the follow In addition, the number of responses can be limited. An offset can also be provided in order to split the results over multiple requests. +>>>> **Ewoud**: Stel ik heb een WP en wil deze aan mijn cloud EMS koppelen, zeg BeNext. Dus dan zoek 'BeNext' op in de public registry via de app van de WP, hoe selecteer ik dan *mijn* EMS instantie? Filter is dan NL/Public status/CEM. + ### DNS-SD based discovery DNS-SD is used to automatically discover nodes from a node that is deployed in the LAN. This method can be used in two ways. -* To discover another node that is deployed in the LAN, which is the responder node -* To advertise a [long polling URL](#long-polling) so other initiator nodes in the LAN could connect to this node +* To discover another node that is deployed in the LAN, which is the Responder node +* To advertise a [long polling URL](#long-polling) so other Initiator nodes in the LAN could connect to this node S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it is an HTTPS based protocol. S2 Connect uses the following DNS-SD values: @@ -432,9 +442,9 @@ The receiver of the service description **must** use the URL provided in the TXT ## The pairing token, the node ID alias and the pairing code -The pairing token is a random string of characters that is generated by the responder node. It is a secret which is transferred by the end user to the initiator node, and then is verified during the pairing process. Since there will be many cases where the end user has to manually type in the pairing token, the pairing token has to be short enough to make it easy for the end user to type in, but long enough to make it secure. +The pairing token is a random string of characters that is generated by the Responder node. It is a secret which is transferred by the end user to the Initiator node, and then is verified during the pairing process. Since there will be many cases where the end user has to manually type in the pairing token, the pairing token has to be short enough to make it easy for the end user to type in, but long enough to make it secure. -The pairing token may consist out of lower case letters, upper case letters and numbers. The pairing token **must** be generated by a cryptographically secure pseudorandom number generator. The pairing token is typically dynamically generated when the user requests the pairing token at the user interface of the responder node. Dynamically generated pairing tokens **must** expire after a duration; five minutes is the recommended duration. However, energy flexible devices that do not have a user interface are allowed to have a static pairing token, that for example can be printed somewhere on the physical device. Static pairing tokens do not expire. Dynamic pairing tokens **must** contain at least 4 characters. Static paring tokens **must** contain at least 6 characters. Pairing tokens may be as long as the developer deems necessary. +The pairing token may consist out of lower case letters, upper case letters and numbers. The pairing token **must** be generated by a cryptographically secure pseudorandom number generator. The pairing token is typically dynamically generated when the user requests the pairing token at the user interface of the Responder node. Dynamically generated pairing tokens **must** expire after a duration; five minutes is the recommended duration. However, energy flexible devices that do not have a user interface are allowed to have a static pairing token, that for example can be printed somewhere on the physical device. Static pairing tokens do not expire. Dynamic pairing tokens **must** contain at least 4 characters. Static paring tokens **must** contain at least 6 characters. Pairing tokens may be as long as the developer deems necessary. | Type of pairing token | Minimal length | Validity | Regular expression | | --- | --- | --- | --- | @@ -466,7 +476,7 @@ Alternatively, the **pairing code** can be validated with the following regular ^([0-9a-zA-Z]+-)?[0-9a-zA-Z]{4,}$ ``` -The pairing code allows us to transfer two pieces of information by only bothering the end user once. Due to its format the initiator node can easily extract the node ID alias and the pairing token from the pairing code by splitting the string at the dash. +The pairing code allows us to transfer two pieces of information by only bothering the end user once. Due to its format the Initiator node can easily extract the node ID alias and the pairing token from the pairing code by splitting the string at the dash. ## Challenge response process @@ -481,7 +491,7 @@ The algorithm to calculate the response is based on the HMAC (hash-based message The HMAC function itself uses a cryptographic hash function for its calculations. Since cryptographic hash functions might contain vulnerabilities, this protocol uses a simple selection mechanism for the cryptographic hash function. The HTTPS client sends with the requestPairing HTTPS request a list of supported hash functions. In the response the HTTPS server indicates which hash function it has selected from this list. This function **must** be used for all response calculations during het pairing attempt. Currently there is only one hash function available (SHA256), but other options might be added in the future. -It order to avoid man-in-the-middle attacks, information about the connection in also used as input for calculating the response. When both nodes are deployed in the LAN, the SHA256 fingerprint of the server certificate is used. In other scenarios, the domain name of the server is included in the calculation of the response. +In order to avoid man-in-the-middle attacks, information about the connection is also used as input for calculating the response. When both nodes are deployed in the LAN, the SHA256 fingerprint of the server certificate is used. In other scenarios, the domain name of the server is included in the calculation of the response. Note that the challenge and response are binary data. Both are encoded using Base64 and must also be decoded before they can be used. SHA256 certificate fingerprints are encoded into a hexadecimal string, and must be decoded as hexadecimal string before it can be used as input (note that fingerprint strings usually contain colons to separate bytes). The pairing token and domain name are strings, which need to be converted into binary data using the ASCII table. @@ -522,7 +532,7 @@ Since these operations are only intended for endpoints within the same LAN, the Once a LAN endpoint has discovered a LAN pairing endpoint (through DNS-SD), it still knows very little about the endpoint. There are two REST operations that allow an HTTPS client to query information of a server endpoint: performing a GET on `/endpoint` and performing a GET on `/nodes`. -These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the initiator node is the HTTPS client and the responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). +These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the Initiator node is the HTTPS client and the Responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). Before the HTTPS client can start interaction with the server, it must first select a version of the API to use. See [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis). For full normative details see the OpenAPI specification files. @@ -535,9 +545,9 @@ Note that there is no authentication for these operations. It is assumed that th > This section is only applicable for LAN-LAN pairing -Once the end user has selected a responder node it wants the initiator node to pair with, the end user probably still has to retrieve the pairing code from the responder node. In order to improve the user experience, the initiator node can send a *prepare pairing* signal to the responder node. The responder node **may** use this signal to proactively show the pairing code in its user interface, for example in the form of a pop-up or notification. This saves the end user the trouble of searching where to find the pairing token in the user interface. It is also possible for the initiator node to send a *cancel prepare pairing* signal to the responder node, in case the end user has no longer selected the responder node it wants to pair with. Sending these signals **must** be implemented by the client, but only when there is a clear distinction between the moment the prepare pairing signal is sent and when the actual pairing starts. The receiver of these signals **may** process these signals by showing the pairing token in its user interface. When the prepare pairing signal is sent, it is not guaranteed that a cancel prepare pairing or a pairing attempt will follow. +Once the end user has selected a Responder node it wants the Initiator node to pair with, the end user probably still has to retrieve the pairing code from the Responder node. In order to improve the user experience, the Initiator node can send a *prepare pairing* signal to the Responder node. The Responder node **may** use this signal to proactively show the pairing code in its user interface, for example in the form of a pop-up or notification. This saves the end user the trouble of searching where to find the pairing token in the user interface. It is also possible for the Initiator node to send a *cancel prepare pairing* signal to the Responder node, in case the end user has no longer selected the Responder node it wants to pair with. Sending these signals **must** be implemented by the client, but only when there is a clear distinction between the moment the prepare pairing signal is sent and when the actual pairing starts. The receiver of these signals **may** process these signals by showing the pairing token in its user interface. When the prepare pairing signal is sent, it is not guaranteed that a cancel prepare pairing or a pairing attempt will follow. -These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the initiator node is the HTTPS client and the responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). +These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the Initiator node is the HTTPS client and the Responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). Note that there is no authentication for these operations. It is assumed that these operations are only exposed within the LAN. @@ -566,7 +576,7 @@ The server **must** perform the checks in the table below. For the checks with H | Is the request properly formatted and does it follow the schema? | 400 | `ParsingError` | | Does it recognize the `serverNodeId`? | 400 | `NodeNotFound` | | Are the endpoint and node ready for pairing? | 400 | `Other` | -| Does the targeted node have a different role than the initiator node (i.e. you cannot pair two RM's or two CEM's)? | 400 | `InvalidCombinationOfRoles` | +| Does the targeted node have a different role than the Initiator node (i.e. you cannot pair two RM's or two CEM's)? | 400 | `InvalidCombinationOfRoles` | | Does the request originate from inside the subnet? | 401 | n/a | If no checks fail the server **should** respond with HTTP status code 204. @@ -588,18 +598,18 @@ The client **must** send the following information in the request. For full norm The server **should** respond with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). However, if the request originated from outside the subnet the server **must** respond with status 401. -### Long-polling +### Long-polling for constrained endpoints in the LAN > This section is only applicable for LAN-LAN pairing The long-polling feature is intended to support endpoints that only want to implement an HTTPS client, and not an HTTPS server. Typically this is because the endpoint runs on constrained hardware. An endpoint is only allowed to only implement the client if it exclusively hosts nodes that have the RM role. -Imagine we have endpoints, one only hosting a CEM node, and one only hosting a RM node. The RM runs on constrained hardware, and only implements the HTTPS client. We have two situations: +Imagine having two endpoints, one only hosting a CEM node, and one only hosting a RM node. The RM runs on constrained hardware, and only implements the HTTPS client. Therefore there are are two situations: -1. **The RM is the initiator node and the CEM is the responder node**: The CEM issues a pairing code, the end users enters the pairing code in the UI of the RM. The RM endpoint (HTTPS client) then sends a normal HTTPS request to the CEM endpoint (HTTPS server) to initiate pairing. The normal pairing process can be used, and long-polling is not required. -2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTPS server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTPS client). +1. **The RM is the Initiator node and the CEM is the Responder node**: The CEM issues a pairing code, the end users enters the pairing code in the UI of the RM. The RM endpoint (HTTPS client) then sends a normal HTTPS request to the CEM endpoint (HTTPS server) to initiate pairing. The normal pairing process can be used, and long-polling is not required. +2. **The CEM is the Initiator node and the RM is the Responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTPS server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTPS client), because the RM endpoint has no HTTPS server implementation. -For the second situation the long-polling feature can be used. It can be used by the initiator node (the HTTPS server) to notify the responder node (the HTTPS client) it wants to pair. +For the second situation the long-polling feature can be used. It can be used by the Initiator node (the HTTPS server) to notify the Responder node (the HTTPS client) it wants to pair. > Informative: Long-polling is a technique that allows the server to send signals to the client without a significant delay, and without relying on additional technologies such as Websockets or Server-Sent Events. The common alternative is polling, where the client sends a request on a regular interval; let's say every 30 seconds. Polling creates a delay from the perspective of the server. If the server wants to send something to the client, it has to wait until the client contacts the server; which in the worst case 30 seconds. With long-polling the server doesn't immediately respond the the request (a hanging HTTPS request). It responds immediately when the server wants the client to do something, or just before the request would time out. After receiving the response from the server the client immediately opens a new request to allow the server to send signals the client again. @@ -612,7 +622,7 @@ The long-polling feature fulfills the following functionality: Before the HTTPS client can start interaction with the server, it must first select a version of the API to use. See [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis). For full normative details see the OpenAPI specification files. -A client capable of long-polling **should** initiates long-polling when it encounters a endpoint through DNS-SD that indicates that is available for long-polling requests. When the endpoint represents zero nodes the client **cannot** attempt long-polling. When the endpoint advertisement itself, or only its long-polling indication disappears from DNS-SD the client **should** stop the long-polling process for that server. The client **must** also stop when it is no longer capable of pairing. +A client capable of long-polling **should** initiates long-polling when it encounters a endpoint through DNS-SD that indicates that is available for long-polling requests. When the endpoint represents zero nodes the client **cannot** attempt long-polling. When the endpoint stops advertising itself, or only its long-polling indication disappears from DNS-SD, the client **should** stop the long-polling process for that server. The client **must** also stop when it is no longer capable of pairing. The server **must** always respond within 25 seconds after receiving the request. The client **must** use a request time-out of at least 30 seconds. @@ -636,7 +646,7 @@ The table below indicates how the client should respond to the requests of the s | 401 | n/a | n/a | Stop long-polling, do not attempt long-polling with this node again | n/a | | 500 | n/a | n/a | Wait before trying to send the next request | Only the `clientNodeId`| -2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTPS server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTPS client). +2. **The CEM is the Initiator node and the RM is the Responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTPS server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTPS client). When the server sends the `requestPairing` action, the node on the client must already have issued a pairing token. If the node uses a dynamic pairing code, it could be the case that the pairing code has expired, or that no pairing code has been issued in the first place. In that case the client **must** perform a new request with an `errorMessage` containing the value `NoValidTokenOnPairingClient` in the object associated with the node ID of the node should have attempted to pair. @@ -706,7 +716,7 @@ Before two node can be paired, the following preconditions must be met. 3. The HTTPS client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) 4. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. -> Note: The initiator node could be the HTTPS server or the HTTPS client +> Note: The Initiator node could be the HTTPS server or the HTTPS client If the HTTPS client does not fulfill these preconditions, it **cannot** send the first HTTPS request of the pairing process. @@ -717,8 +727,8 @@ The client sends the following information (for full details see the OpenAPI spe | Information | Description | | --- | --- | -| `clientNodeDescription` | Information about the node that wants to pair, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the initiator node | -| `clientEndpointDescription` | Information about the client endpoint. An important field is the deployment. | +| `clientNodeDescription` | Information about the node that wants to pair, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the Initiator node | +| `clientEndpointDescription` | Information about the client endpoint. An important field is the deployment (LAN or WAN). | | `nodeId` | The nodeID of the node that is being targeted (this filed can be omitted if the client only knows the `nodeIdAlias` or when the endpoint only represents one node). | | `nodeIdAlias` | The nodeIdAlias of the node that is being targeted (this field can be omitted if the client only knows the `nodeId` or when the endpoint only represents one node) | | `supportedCommunicationProtocols` | List of supported communications protocols of the client | @@ -747,17 +757,17 @@ The server **must** perform the checks in the table below to make sure that it c | Does it recognize the `nodeIdAlias`? | `NodeNotFound` | No | | Are the endpoint and node ready for pairing? | `Other` | No | | If no `nodeIdAlias` provided, does this endpoint indeed only represent one node? | `NoNodeIdProvided` | No | -| Does the targeted node have a different role than the initiator node (i.e. you cannot pair two RM's or two CEM's)? | `InvalidCombinationOfRoles` | No | +| Does the targeted node have a different role than the Initiator node (i.e. you cannot pair two RM's or two CEM's)? | `InvalidCombinationOfRoles` | No | | Does the server accept any of the provided hashing algorithms for the challenge response process? | `IncompatibleHmacHashingAlgorithms` | No | | Is there overlap between the communication protocols? | `IncompatibleCommunicationProtocols` | Yes | | Is there overlap between the S2 message versions? | `IncompatibleS2MessageVersions` | Yes | -| If the targeted node on the HTTPS server is the initiator node, did the end user provide a valid pairing token? | `NoValidPairingTokenOnPairingServer` | No | -| If the targeted node on the HTTPS server is the responder node, does the node have a pairing token which has not expired? | `NoValidPairingTokenOnPairingServer` | No | +| If the targeted node on the HTTPS server is the Initiator node, did the end user provide a valid pairing token? | `NoValidPairingTokenOnPairingServer` | No | +| If the targeted node on the HTTPS server is the Responder node, does the node have a pairing token which has not expired? | `NoValidPairingTokenOnPairingServer` | No | | Is this is a WAN pairing server for a LAN endpoint, does the client have a WAN deployment? | `Other` | No | > Note: If the node that is being paired is an RM which is already paired, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation must be unpaired. -> Note: If the targeted node is already paired with the initiator node, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation is maintained. +> Note: If the targeted node is already paired with the Initiator node, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation is maintained. > Note: This is the only step where it is checked if the pairing code has expired. If the pairing token expires after this step, but during the pairing process, the pairing process will continue. A pairing attempt is limited to 15 seconds. @@ -776,7 +786,7 @@ The server responds with the following information (for full details see the Ope | Information | Description | | --- | --- | | `pairingAttemptId` | The generated identifier for this pairing attempt | -| `serverNodeDescription` | Information about the node that is being targeted, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the responder node | +| `serverNodeDescription` | Information about the node that is being targeted, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the Responder node | | `serverEndpointDescription` | Information about the server endpoint. An important field is the deployment. | | `selectedHmacHashingAlgorithm` | The hashing algorithm for the challenge response function as selected in step 2 | | `clientHmacChallengeResponse` | The response to the challenge provided by the HTTPS client as calculated in step 2 | @@ -830,7 +840,7 @@ The HTTPS server checks the `serverHmacChallengeResponse` provided by the HTTPS If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTPS client anymore. If the HTTPS client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). ### 8A. Response status 200 -The server **must** generates an access token for the HTTPS client. The access token is random binary data and **must** be generated by a cryptographically secure pseudorandom number generator and **must** have a minimum length of 32 bytes. It is encoded using Base64. The access token **cannot** be used by the initiator node until the pairing process is completed. +The server **must** generates an access token for the HTTPS client. The access token is random binary data and **must** be generated by a cryptographically secure pseudorandom number generator and **must** have a minimum length of 32 bytes. It is encoded using Base64. The access token **cannot** be used by the Initiator node until the pairing process is completed. The server responds with two pieces of information: @@ -844,7 +854,7 @@ If the response is understood and properly formatted, the HTTPS client **should* ### 6B. POST /[version]/postConnectionDetails > Note: The `pairingAttemptId` must be provided through a header for this HTTPS request -The HTTPS sends the connection details to the HTTPS server. This request also serves as a way to send the HTTPS server the `serverHmacChallengeResponse` calculated in step 5. +The HTTPS client sends the connection details to the HTTPS server. This request also serves as a way to send the HTTPS server the `serverHmacChallengeResponse` calculated in step 5. In this case the pairing server will become the communication client. Once the pairing server becomes the communication client, it does not know what the certificate that the communication server will use. That is why it needs to provide the fingerprint of its CA (root) certificate using the property `certificateFingerprint`. This property is a map, where the key of the map is the hashing algorithm used to generate the fingerprint, and the value is the fingerprint itself. The hashing function `SHA256` and the related fingerprint **must** always be provided. The communication client **must** pin this certificate to the domain name of the communication server. @@ -919,7 +929,7 @@ If the server receives a wrong HTTPS request (e.g. `/postConnectionDetails` whil After two nodes have been paired, the nodes exchange S2 messages over a secure connection. -The following mechanism **must** be used to initiate a secure connection between two nodes. Client authentication is based on a one-time use communication token that needs to be renewed every time a new S2 session is created. The communication client will always attempt to set up an S2 connecting with the communication server when there is no connection. For more details see [Reconnection strategy](#reconnection-strategy). +The following mechanism **must** be used to initiate a secure connection between two nodes. Client authentication is based on a one-time use communication token that needs to be renewed every time a new S2 session is created. The communication client will always attempt to set up an S2 connection with the communication server when there is no connection. For more details see [Reconnection strategy](#reconnection-strategy). ## Mapping the CEM and RM to communication server or client @@ -1122,7 +1132,7 @@ Communication over the WebSocket endpoint **must** be encrypted following [RFC 6 The WebSocket Protocol ([RFC6455](https://datatracker.ietf.org/doc/html/rfc6455)) has an extension for compression: [**RFC 7692**](https://datatracker.ietf.org/doc/html/rfc7692.html) implementing so called per-message-deflate compression. https://datatracker.ietf.org/doc/html/rfc7692 -RFC 7692 is widely supported by WebSocket libraries and and we are exchanging JSON plain text messages, it is expected to save a large amount of data. Therefore, implementations of S2 WebSockets **SHOULD** support RFC 7692 and **SHOULD** enable it whenever possible. +The RFC 7692 compression extension is widely supported by WebSocket libraries and since JSON is exchanged as plain text messages, it is expected to save a large amount of data. Therefore, implementations of S2 WebSockets **SHOULD** support RFC 7692 and **SHOULD** enable it whenever possible. ### Keepalive & heartbeat (ping / pong) WebSockets by default have a **keepalive** and a **heartbeat mechanism**. Keepalive is designed to keep the connection open while heartbeat is designed to check the latency and check the connection is still working. This means that periodically a ping frame is sent to the server (endpoint) and in response a pong frame is sent. @@ -1182,6 +1192,8 @@ WebSocketDisconnected --> [*] | ControlType FRBC activated | FRBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | FRBC.ActuatorStatus
FRBC.FillLevelTargetProfile
FRBC.LeakageBehaviour
FRBC.StorageStatus
FRBC.SystemDescription
FRBC.UsageForecast
FRBC.TimerStatus
RevokeObject
InstructionStatusUpdate ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | | ControlType DDBC activated | DDBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | DDBC.ActuatorStatus
DDBC.AverageDemandRateForecast
DDBC.SystemDescription
DDBC.TimerStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement PowerForecast
SessionRequest
ReceptionStatus | +>>>> **Ewoud**: Ook `NO_CONTROL_TYPE` toevoegen? + # Unpairing process (normative) Unpairing can be initiated by either node, and **should** only be done when instructed by the end user. The node that did not take the initiative to unpair **should** try to inform the end user that the node is no longer paired. @@ -1262,4 +1274,4 @@ With the exemption of some cases where the authenticity cannot be verified yet, ## Cipher suites -Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. +Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. \ No newline at end of file diff --git a/website/static/img/communication-layer/powerpoint_img_source.pptx b/website/static/img/communication-layer/powerpoint_img_source.pptx index ee15a09971cc07c39e2cdf222eeed44da3e5db7f..b28d775a86e2a122bce7007fcc7de93adaed3429 100644 GIT binary patch delta 51640 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%0AnLrUbU1<6=-3=5D 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 delta 16049 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 Xg@4{90{~t>{~0}dBcs!EJAnTKB;Y>n From 2f6801df60a8dda8a0e9cfdab1e01d6ebded6f1f Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 25 Jun 2026 14:29:00 +0200 Subject: [PATCH 55/67] Rephrased HTTP GET request to root URL --- .../discovery-pairing-authentication.md | 8 +++----- .../api_version_selection.png | Bin 5891 -> 5345 bytes 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 3929f7f..cd24657 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -280,8 +280,6 @@ The image below depicts the interactions between client and server for the proce ![image](@site/static/img/communication-layer/api_version_selection.png) ->>>> **Ewoud**: GET / is een beetje verwarrend als de URL bijv. https://device.local/s2c/ is. Dus GET / vervangen door GET root of supplied URL? -
Image generated using the following PlantUML code: @@ -290,7 +288,7 @@ The image below depicts the interactions between client and server for the proce participant "HTTPS Client" as Client participant "HTTPS Server" as Server -Client->Server++: 1. GET / (index containing pairing API versions) +Client->Server++: 1. GET to the Pairing URL Server-->Client: 2. Response status 200 deactivate Server Client->Client: 3. Decide pairing version @@ -299,8 +297,8 @@ Client->Client: 3. Decide pairing version ```
-### 1. GET / (index containing pairing API versions) -Since the HTTPS client does not know which major versions of the pairing API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). +### 1. GET on the Pairing URL +Since the HTTPS client does not know which major versions of the pairing API are implemented by the server, it must first perform a GET request to the [Pairing URL](#pairing-url) (e.g. `https://hostname.local/pairing/`). The client **must** perform the following checks during this request: diff --git a/website/static/img/communication-layer/api_version_selection.png b/website/static/img/communication-layer/api_version_selection.png index 28e21c74d02e28627b23c4a0f5999cebf0d058de..de15c49126a36a9065e76fe79d47e87fc2835efc 100644 GIT binary patch delta 5156 zcmZ9QWmHsc+pr0x2ap=NRJx=Eq(O2BDd~=Z0i=6~p%LkBM5J4~Bt#rinxVU!0VH00 z?&to#^}gqieeJc^+WV???DIUX5Uv$gsYrFuTL2Cv4hjkiKv6+f69olT;PJi-3;pq` zx$}hOv6)d<(UN;?F)^`laBzTsgZwdQY3Uy$UP=_zGu~ex4Gxc=r6xQ&IyyW&Mk0|9 z4-ci@gawIDQD37rCrY4_LZxtnB?&{1{;-Ett`@M9coF6o;M2$+U^<9Q1Xlby+&&u7 zZ+FiSI&SxNQW}{%Ij4mgF}Yhdn9Uf?e*-TI6H+b)U6$Qq3T#`uNw6p|piu+OJ{3@F zVyC*|ilKE9vZ=C2d}sM`RQMwb3eitRSt%{AxdS8Ick>D@ zcb2pLy!}j@8!!|G?8jhGaojx|RvEGeA1*AlY1hrpHYaALJJ80(VG`2EkTmN{>D_P4nx!aIDK;#& zM=YbjvAThLJ#te4XSwS=BDc+&V6w4yW1f=Cj#iFu0L-No+O(@9-}f-vt4>~k93u+U z7Vt;P4{8(0+k32*6cHb+3^R=a-V?u$|1|PS4qm3^G>2rGh>ZL?eA@x?bJm@ws08u{e810M=HG)<)<&bg8ag1VbhU~KLr|0n zr#;97R!z+uv70{YqxWLeedUcM#R;E`a6c3bk#PmvPD)_MDd8YwseiAkhf(Ahii-I> z{cXscT&70N0gnt+yb}K9;^_QXKmzv)%0ABJ3uPV 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 F{{zzn>ec`N From f9b42618d0c0df69b2a7f123ee38a20479b6ddbb Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 25 Jun 2026 14:37:26 +0200 Subject: [PATCH 56/67] Clarification NO_CONTROL_TYPE --- website/s2-connect/discovery-pairing-authentication.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index cd24657..c69fb8b 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -370,7 +370,7 @@ The registry contains the following information for each endpoint. For full norm | `id` | Unique UUID identifier for the record | | `name` | User facing name of the endpoint | | `description` | User facing description of the endpoint| -| `icon32` | 32 by 32 pixel- icon of the endpoint | +| `icon32` | 32 by 32 pixels icon of the endpoint | | `icon128` | 128 by 128 pixels icon of the endpoint | | `icon512` | 512 by 512 pixels icon of the endpoint | | `pairingUrl` | The pairing URL of the endpoint | @@ -1181,6 +1181,8 @@ WebSocketDisconnected --> [*] ```
+> Note: When no Control Type is selected (i.e. `NO_CONTROL_TYPE`), the protocol is in the "WebSocket Connected" state. + | State | Messages that can be sent by CEM /received by RM | Messages that can be sent by RM / received by CEM | | --- | --- | --- | | WebSocket Connected | SelectControlType
SessionRequest
ReceptionStatus | ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | @@ -1190,8 +1192,6 @@ WebSocketDisconnected --> [*] | ControlType FRBC activated | FRBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | FRBC.ActuatorStatus
FRBC.FillLevelTargetProfile
FRBC.LeakageBehaviour
FRBC.StorageStatus
FRBC.SystemDescription
FRBC.UsageForecast
FRBC.TimerStatus
RevokeObject
InstructionStatusUpdate ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | | ControlType DDBC activated | DDBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | DDBC.ActuatorStatus
DDBC.AverageDemandRateForecast
DDBC.SystemDescription
DDBC.TimerStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement PowerForecast
SessionRequest
ReceptionStatus | ->>>> **Ewoud**: Ook `NO_CONTROL_TYPE` toevoegen? - # Unpairing process (normative) Unpairing can be initiated by either node, and **should** only be done when instructed by the end user. The node that did not take the initiative to unpair **should** try to inform the end user that the node is no longer paired. From 019a1792a3a403894c22bc31e8fbea5c089c3128 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 9 Jul 2026 09:39:49 +0200 Subject: [PATCH 57/67] Clarified that endpoint URL and pairing code is all the info needed to initiate pairing --- website/s2-connect/discovery-pairing-authentication.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index c69fb8b..db6d4e0 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -136,7 +136,7 @@ The first step of pairing is establishing a connection from the Initiator node t * If the Responder node is deployed in the WAN, the URL could be retrieved through a registry. The end user would have to select the type of node from a list of known node services in its region. * If both nodes are deployed in the LAN however, nodes can be automatically be discovered. The end user would have to select the node from a list of automatically discovered nodes. -The second step is entering the pairing code of the Responder node. This is a means for the end user to confirm that these two nodes are allowed to send control signals to each other. The pairing code can be obtained from the Responder node. The pairing code is a (seemingly) random string of characters. This pairing code is typically displayed somewhere in the user interface of the Responder node. It is recommended to use a dynamic token which expires after 5 minutes. However, if the Responder node has a physical presence in the building and doesn't have user interface, there is also the option to have a static pairing code which can be printed on the device. +The second step is entering the pairing code of the Responder node. This is a means for the end user to confirm that these two nodes are allowed to send control signals to each other. The pairing code can be obtained from the Responder node. The pairing code is a (seemingly) random string of characters. This pairing code is typically displayed somewhere in the user interface of the Responder node. It is recommended to use a dynamic token which expires after 5 minutes. However, if the Responder node has a physical presence in the building and doesn't have user interface, there is also the option to have a static pairing code which can be printed on the device. The pairing code serves two purposes: It is both an identifier of the node, as well as a secret that carries the trust relationship from one node, through the end user, to the other node. Optionally, the Initiator node can send a signal to the Responder node to indicate that the end user has started the pairing process and has selected the Responder node. This could trigger the user interface of the Responder node to proactively show the pairing code (e.g. through a pop-up) to improve the user experience. @@ -175,9 +175,9 @@ iui->e: Pairing result (success or failure) Within this protocol a clear distinction is made between two types of identities: the one of the **Endpoint** and the one of the **Node** itself. -A **Node** is an instance of a CEM or a RM. It is either a specific energy management system or service, or the representative of a physical energy flexible device, such as a heat pump or a home battery. It is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. +A **Node** is an instance of a CEM or a RM. A CEM instance typically belongs to one end user, building or premise, and could be hosted in a dedicated energy management device. A RM instance is typically the representative of one physical energy flexible device, such as a heat pump or a home battery. A node is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. -The **Endpoint** is the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. +The **Endpoint** is the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host thousands of nodes. It could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. ## Used technology for pairing and communication @@ -388,7 +388,7 @@ In order to filter out the relevant endpoint records the API supports the follow In addition, the number of responses can be limited. An offset can also be provided in order to split the results over multiple requests. ->>>> **Ewoud**: Stel ik heb een WP en wil deze aan mijn cloud EMS koppelen, zeg BeNext. Dus dan zoek 'BeNext' op in de public registry via de app van de WP, hoe selecteer ik dan *mijn* EMS instantie? Filter is dan NL/Public status/CEM. +> Note: Since the pairing code also contains an identifier for the node within the endpoint, the combination of pairing code and endpoint URL is sufficient information to start a pairing attempt. ### DNS-SD based discovery DNS-SD is used to automatically discover nodes from a node that is deployed in the LAN. This method can be used in two ways. From 04f4a17b080548732a37ef0f28652b5daa823165 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 9 Jul 2026 10:05:50 +0200 Subject: [PATCH 58/67] Improved overview image for pairing --- .../discovery-pairing-authentication.md | 3 +-- .../communication-layer/pairing_direction.png | Bin 64843 -> 77621 bytes .../powerpoint_img_source.pptx | Bin 203353 -> 204588 bytes 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index db6d4e0..f4c9788 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -238,8 +238,7 @@ There are however two situations where this is not possible: ![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) ->>>> **Ewoud**: Plaatje opsplitsen want dit is de veel pijltjes. Alternatief voor WAN-LAN heb ik gemaakt in powerpoitn - +**Figure:** Overview of all possible ways to pair two nodes # Formal specification and versioning (normative) diff --git a/website/static/img/communication-layer/pairing_direction.png b/website/static/img/communication-layer/pairing_direction.png index 9c9344fa34b274e38d5ba55c7fa6e1c9e848a100..072137b8009e2815a72dd8503fddb08742212837 100644 GIT binary patch literal 77621 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 T5~oLWW$)gWyOnuE@9F;n>IIsP literal 64843 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 From 3ca3c594dd0500429de774b9949e326088e05ca3 Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 9 Jul 2026 10:36:16 +0200 Subject: [PATCH 59/67] Version bump --- website/s2-connect/discovery-pairing-authentication.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index f4c9788..e35e631 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -5,6 +5,8 @@ sidebar_position: 2 # S2 Connect - Specification +Version: v1.0 + # Introduction S2 Connect is a JSON-based protocol specification implementing the EN50491-12-2 "S2" standard for home and building energy management. @@ -19,8 +21,8 @@ This version of this specification is based on the following versions of the und | Project | Files | Version | Reference | | --- | --- | --- | --- | -| S2 Connect | OpenAPI files | `v1.0-beta-2` | [Github](https://github.com/flexiblepower/s2-connect) | -| S2 JSON | JSON schemas | `v0.02-beta` | [Github](https://github.com/flexiblepower/s2-ws-json) | +| S2 Connect | OpenAPI files | `v1.0` | [Github](https://github.com/flexiblepower/s2-connect) | +| S2 JSON | JSON schemas | `v1.0.0` | [Github](https://github.com/flexiblepower/s2-ws-json) | > Note: S2 Connect is not directly linked to the version of S2 JSON. The exact version of S2 JSON that is being used by the CEM and RM is negotiated during session initiation. From 19a9ec3621de65a01c287156ac4dda2a48fa10bf Mon Sep 17 00:00:00 2001 From: Wilco Wijbrandi Date: Thu, 9 Jul 2026 10:54:23 +0200 Subject: [PATCH 60/67] Update discovery-pairing-authentication.md --- website/s2-connect/discovery-pairing-authentication.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index e35e631..7881049 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -5,7 +5,7 @@ sidebar_position: 2 # S2 Connect - Specification -Version: v1.0 +Version: v1.0.0 # Introduction @@ -1273,4 +1273,4 @@ With the exemption of some cases where the authenticity cannot be verified yet, ## Cipher suites -Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. \ No newline at end of file +Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. From 61e230070d23c98d4c580e5d0adc9e8b42c9cea8 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 9 Jul 2026 21:13:56 +0200 Subject: [PATCH 61/67] fix link to paths outside of the s2c docs plugin and move images to s2-connect folder --- .../discovery-pairing-authentication.md | 22 +- .../img}/api_version_selection.png | Bin .../img}/certificate-chains.png | Bin .../img}/deployment_options.png | Bin .../img}/long-polling_activity_diagram.drawio | 0 .../img}/long-polling_activity_diagram.png | Bin .../img}/pairing_direction.png | Bin .../img}/pairing_http_process.png | Bin .../img}/pairing_process_user.png | Bin .../img}/powerpoint_img_source.pptx | Bin .../img}/session-initiation.png | Bin .../img}/state-of-communication.png | Bin .../discovery-pairing-authentication.md | 1210 ----------------- 13 files changed, 11 insertions(+), 1221 deletions(-) rename website/{static/img/communication-layer => s2-connect/img}/api_version_selection.png (100%) rename website/{static/img/communication-layer => s2-connect/img}/certificate-chains.png (100%) rename website/{static/img/communication-layer => s2-connect/img}/deployment_options.png (100%) rename website/{static/img/communication-layer => s2-connect/img}/long-polling_activity_diagram.drawio (100%) rename website/{static/img/communication-layer => s2-connect/img}/long-polling_activity_diagram.png (100%) rename website/{static/img/communication-layer => s2-connect/img}/pairing_direction.png (100%) rename website/{static/img/communication-layer => s2-connect/img}/pairing_http_process.png (100%) rename website/{static/img/communication-layer => s2-connect/img}/pairing_process_user.png (100%) rename website/{static/img/communication-layer => s2-connect/img}/powerpoint_img_source.pptx (100%) rename website/{static/img/communication-layer => s2-connect/img}/session-initiation.png (100%) rename website/{static/img/communication-layer => s2-connect/img}/state-of-communication.png (100%) delete mode 100644 website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 7881049..6883851 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -80,7 +80,7 @@ Non-repudiation is explicitly not guaranteed by this protocol. The entire trust model of S2 Connect is based on the fact that there is already a trust relation between the end user and the CEM and RM. If the CEM and RM do not use adequate security mechanisms, it might be possible to attack the system. ->> "Why doesn't S2 Connect use oAuth?" is a common question. oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. There is a way to make it work, but since it is such a non-typical way the decision was made to not use oAuth 2.0 for pairing. For more details see [here](why-not-oauth.md). +>> "Why doesn't S2 Connect use oAuth?" is a common question. oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. There is a way to make it work, but since it is such a non-typical way the decision was made to not use oAuth 2.0 for pairing. For more details see here. # Terms and definitions (normative) @@ -122,7 +122,7 @@ Nodes can be deployed locally within the LAN, or somewhere on a server in the WA * **WAN** nodes are typically part of a large application that run on many servers and/or on some kind of cloud computing platform. A single application usually serves many users. Each user could have one or multiple nodes. This could for example be a cloud-based energy management system that can connect to many devices. It could also be a cloud environment of a device manufacturer that hosts the RM instances in the cloud. An end user could own multiple devices from this manufacturer, thus the application could host multiple RM instances for this particular user. A group of nodes that a single user can manage within one application is called an *end user environment*. It is also possible that an end user environment contains both CEM and RM instances. The user interface is typically a web interface or a smartphone app. * **LAN** nodes are typically part of an application that runs on an embedded computer device somewhere in the building. Such a device could be a physical energy management system, an energy flexible device such as a home battery, heat pump or EV charger, or a gateway device which connects to an energy flexible device through some kind of protocol. Often an application will only host a single node, but it is also possible that an application hosts multiple nodes. A device could function completely on its own, but it could also be connected to an internet based application of the manufacturer. The user interface could be a physical human-machine interface on the device, but also be a smartphone app that connects directly to the device (e.g. via Bluetooth), or a smartphone app or web interface that connects to an internet based application of the manufacturer. For energy flexible devices, it is assumed that they could also have no user interface at all, or that they are very constrained when it comes to computing power or memory. It is assumed that a CEM always has a user interface. -![Deployment_options](@site/static/img/communication-layer/deployment_options.png) +![Deployment_options](./img/deployment_options.png) There are three types of S2 connections between nodes possible: @@ -150,7 +150,7 @@ Once a CEM is paired, the user has the possibility to command either of the node The pairing process is depicted in the figure below: -![Pairing_process_user](@site/static/img/communication-layer/pairing_process_user.png) +![Pairing_process_user](./img/pairing_process_user.png)
Image generated using the following PlantUML code: @@ -238,7 +238,7 @@ There are however two situations where this is not possible: * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTPS server in the cloud, even though the node itself is in the LAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients and must not be used by LAN clients. There must always be a method for purely LAN based pairing. * **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTPS server is far more memory intensive than an HTTPS client, there is an option to implement a LAN RM instance purely as an HTTPS client. A long-polling mechanism is available to indicate to the HTTPS server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTPS server. In other words: in this specific situation the Initiator node behaves as the HTTPS server, and the Responder node only has to be an HTTPS client. -![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) +![Pairing_direction](./img/pairing_direction.png) **Figure:** Overview of all possible ways to pair two nodes @@ -279,7 +279,7 @@ As explained in the section [Versioning of OpenAPI files](#version) the pairing The image below depicts the interactions between client and server for the process to determine the API version that will be used. -![image](@site/static/img/communication-layer/api_version_selection.png) +![image](./img/api_version_selection.png)
Image generated using the following PlantUML code: @@ -651,13 +651,13 @@ When the server sends the `requestPairing` action, the node on the client must a The activity diagram below summarizes the complete long-polling process from the perspective of the client. -![image](@site/static/img/communication-layer/long-polling_activity_diagram.png) +![image](./img/long-polling_activity_diagram.png) ## Pairing interaction The pairing process itself consists of several HTTPS interactions between client and server. The image below depicts a successful pairing process between two nodes. -![image](@site/static/img/communication-layer/pairing_http_process.png) +![image](./img/pairing_http_process.png)
Image generated using the following PlantUML code: @@ -952,7 +952,7 @@ There are four scenarios for CEM and RM deployment, and applying the rules above During the pairing process an `accessToken` is generated by the node which will be the communication server and sent to the node that will be the communication client. This `accessToken` can be used by the communication client to set up a session with the communication server for exchanging S2 messages. The `accessToken` does not expire, but it can only be used (successfully) once to set up a session. Each time a new session is made, the `accessToken` will be renewed. The communication server will generate a new `accessToken` and sends it to the communication client. Since this `accessToken` is the only means to connect two nodes once they are paired, the session initiation process makes sure that both nodes confirm that they have successfully persisted the new `accessToken` before invalidating the old `accessToken`. -![session initiation](@site/static/img/communication-layer/session-initiation.png) +![session initiation](./img/session-initiation.png)
Image generated using the following PlantUML code: @@ -1156,7 +1156,7 @@ When using S2 Connect, the `Handshake` and `HandshakeResponse` messages (as defi ### State of communication -![State of Communication](@site/static/img/communication-layer/state-of-communication.png) +![State of Communication](./img/state-of-communication.png)
Image generated using the following PlantUML code: @@ -1222,7 +1222,7 @@ There are two possible types of certificates for TLS communication. The first op The following image shows the difference. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. -![image.png](@site/static/img/communication-layer/certificate-chains.png) +![image.png](./img/certificate-chains.png)
Image generated using the following PlantUML code: @@ -1273,4 +1273,4 @@ With the exemption of some cases where the authenticity cannot be verified yet, ## Cipher suites -Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. +Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in Accepted crypto algorithms. This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. diff --git a/website/static/img/communication-layer/api_version_selection.png b/website/s2-connect/img/api_version_selection.png similarity index 100% rename from website/static/img/communication-layer/api_version_selection.png rename to website/s2-connect/img/api_version_selection.png diff --git a/website/static/img/communication-layer/certificate-chains.png b/website/s2-connect/img/certificate-chains.png similarity index 100% rename from website/static/img/communication-layer/certificate-chains.png rename to website/s2-connect/img/certificate-chains.png diff --git a/website/static/img/communication-layer/deployment_options.png b/website/s2-connect/img/deployment_options.png similarity index 100% rename from website/static/img/communication-layer/deployment_options.png rename to website/s2-connect/img/deployment_options.png diff --git a/website/static/img/communication-layer/long-polling_activity_diagram.drawio b/website/s2-connect/img/long-polling_activity_diagram.drawio similarity index 100% rename from website/static/img/communication-layer/long-polling_activity_diagram.drawio rename to website/s2-connect/img/long-polling_activity_diagram.drawio diff --git a/website/static/img/communication-layer/long-polling_activity_diagram.png b/website/s2-connect/img/long-polling_activity_diagram.png similarity index 100% rename from website/static/img/communication-layer/long-polling_activity_diagram.png rename to website/s2-connect/img/long-polling_activity_diagram.png diff --git a/website/static/img/communication-layer/pairing_direction.png b/website/s2-connect/img/pairing_direction.png similarity index 100% rename from website/static/img/communication-layer/pairing_direction.png rename to website/s2-connect/img/pairing_direction.png diff --git a/website/static/img/communication-layer/pairing_http_process.png b/website/s2-connect/img/pairing_http_process.png similarity index 100% rename from website/static/img/communication-layer/pairing_http_process.png rename to website/s2-connect/img/pairing_http_process.png diff --git a/website/static/img/communication-layer/pairing_process_user.png b/website/s2-connect/img/pairing_process_user.png similarity index 100% rename from website/static/img/communication-layer/pairing_process_user.png rename to website/s2-connect/img/pairing_process_user.png diff --git a/website/static/img/communication-layer/powerpoint_img_source.pptx b/website/s2-connect/img/powerpoint_img_source.pptx similarity index 100% rename from website/static/img/communication-layer/powerpoint_img_source.pptx rename to website/s2-connect/img/powerpoint_img_source.pptx diff --git a/website/static/img/communication-layer/session-initiation.png b/website/s2-connect/img/session-initiation.png similarity index 100% rename from website/static/img/communication-layer/session-initiation.png rename to website/s2-connect/img/session-initiation.png diff --git a/website/static/img/communication-layer/state-of-communication.png b/website/s2-connect/img/state-of-communication.png similarity index 100% rename from website/static/img/communication-layer/state-of-communication.png rename to website/s2-connect/img/state-of-communication.png diff --git a/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md b/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md deleted file mode 100644 index 291086e..0000000 --- a/website/s2c_versioned_docs/version-1.0-beta2/discovery-pairing-authentication.md +++ /dev/null @@ -1,1210 +0,0 @@ ---- -title: S2 Connect Specification -sidebar_position: 2 ---- - -# S2 Connect - Specification - -# Introduction - -S2 Connect is a JSON based protocol specification implementing the EN50491-12-2 "S2" standard for home and building energy management. - -This specification addresses everything needed to created a secure and interoperable implementation of the S2 standard. The communication layer concerns the discovery, the pairing, the application layer communication protocol, the authentication, the message data model and the serialization. It was specifically designed to support multiple deployment scenario's and to give a relative consistent user experience throughout these different scenario's. - -The protocol is designed to specify communication between two devices, a resource (e.g. a heat pump or EV charger) and a (home) energy management system. It is worth noting that, while this specification focuses on describing the interaction between two components, a (home) energy management is likely to be communicating with multiple resources at the same time. - -# Version - -This version of this specification is based on the following versions of the underlying formal specification files (see [Formal specification and versioning (normative)](#formal-specification-and-versioning-normative) for more details). - -| Project | Files | Version | Reference | -| --- | --- | --- | --- | -| S2 Connect | OpenAPI files | `v1.0-beta-2` | [Github](https://github.com/flexiblepower/s2-connect) | -| S2 JSON | JSON schemas | `v0.02-beta` | [Github](https://github.com/flexiblepower/s2-ws-json) | - -> Note: S2 Connect is not directly linked to the version of S2 JSON. The exact version of S2 JSON that is being used by the CEM and RM is negotiated during connection initiation. - -# List of abbreviations - -|Abbreviation | Meaning -|---|---| -| CEM | Customer Energy Manager | -| DNS | Domain Name System | -| DNS-SD | DNS Service Discovery | -| HTTP | HyperText Transfer Protocol | -| LAN | Local Area Network (i.e. a local network, typically constrained to the building) | -| mDNS | Multicast DNS | -| NAT | Network Address Translation | -| REST | Representational state transfer | -| RM | Resource Manager | -| S2 | European standard on Energy Flexibility EN50491-12-2 | -| UUID | Universally Unique IDentifier (see [RFC 9562](https://www.rfc-editor.org/rfc/rfc9562)) | -| WAN | Wide Area Network (i.e. the public internet) | - -# Background (informative) - -## Requirements - -> TODO: This section needs to be rewritten to better explain the design goals - -The communication layer meets the following requirements: - -The Customer Energy Manager (CEM) and Resource Manager (RM) are logical concepts within the S2 architecture, therefore the S2 standard does not make any assumptions on how and where the CEM and RM are deployed in a real life situation. In practice, the CEM could be deployed on a local gateway in a LAN or as a server somewhere on the internet (WAN), while the RM could be part of the device itself, deployed on an add-on module or on the internet as well. This means that the S2 communication layer **MUST** be able to deal with multiple scenarios that are depicted in the figure below. - -In addition to - and partly because of - supporting the various deployment options, the S2 communication layer has the following generic requirements: - -- Support for full duplex communication. Both sides **MUST** be able to send and receive data simultaneously. -- Communication **MUST** be IP based. -- Communication **MUST** be encrypted. -- Communication latency between CEM and RM or vice versa **MUST** be ≤ 1 second. -- Communication **MUST** work without additional firewall configuration by the end user. -- Implementation of the communication layer **MUST** be based on a widely accepted technology and must be relatively easy to implement. -- The pairing process **SHOULD** support extensibility for other application layer communication protocols. -- Provide a relatively consistent user experience regardless of the deployment of the node -- Run a local RM on a device with constrained hardware -- A RM could not have a UI - -## Technical decisions -Given the requirements, this specification is build on the following high-level technical choices: - -Application layer communication protocol: WebSocket Secure with bearer token authentication. - -Pairing: Custom HTTP API specified in OpenAPI. - -Discovery: DNS-SD (within a LAN) in combination with a central registry (for WAN deployments). - -Serialization: json. - - -**Why not oAuth 2.0?** - -The short answer is: oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. We have identified a way to make it work but since it is such non-typical way, we choose not to use oAuth 2.0. - -For the long answer, please refer to [this page](why-not-oauth.md). - -## Security requirements - -The S2 Connect protocol ensures the following four requirements: - -1. Mutual authentication -2. Integrity of communication -3. Confidentiality of communication -4. Forward secrecy - -There is one guarantee that explicitly is not given by this protocol: - -5. Non-repudiation - - -### 1. Mutual authentication (guaranteed) - -The mutual authentication is based on the trust relation between the user and the Client/Server. Since it is assumed that the user already had a trust relation with both of them, this existing trust can be used for mutual authentication between the client and the server. Note that the this communication is not part of the S2 Connect specification. - -The end user requests a URL, and token from the server, and gives these to the client. Based on these data, the client can connect to the server, using a TLS connection, check the certificate and authenticate himself with the token. Note that if the server uses a self-signed certificate, the fingerprint will be shared during the pairing phase, so it can be verified by the client. - -### 2. Integrity of communication (guaranteed) - -Using TLS will ensure the integrity of the data. - -### 3. Confidentiality of communication (guaranteed) - -Using TLS will ensure the confidentiality of the data. - -### 4. Forward secrecy (guaranteed) - -Using TLS1.3 will ensure the forward secrecy of the data. - -### 5. Non-repudiation (NOT guaranteed) - -Non-repudiation is not guaranteed in this protocol. Individual messages are not signed by anyone and as a result both parties could deny sending a specific request. However, while no legal proof is given, since integrity and authenticity is guaranteed by TLS, each party always knows for sure which party made what statement. - -### Remaining risk - -There are two remaining vulnerable situations. In this section both will be explained. - -#### self-signed certificates - -In the case that a local RM and a local CEM communicate, it is not in every situation possible to generate a PKI-certificate that can be publicly validated. As a result, S2 accepts, **ONLY** in this situation, self-signed certificates. The risk for spoofing attacks are mitigated by including the certificate fingerprint in the challenge-response process as part of the pairing process, and pinning the self-signed CA certificate at the client side. As a result, the client can check for all connections whether or not it is connected with the correct server. - -#### Trust relations between the end-user and the Client/Server - -The entire trust model of S2 Connect is based on the fact that there is already a trust relation between the end-user and the client/server. If these clients/servers do not use adequate security mechanisms, it might be possible to attack the S2 system as well. - -# Terms and definitions (normative) - -This specification uses the concepts that are defined below. - -| Term | Definition | -| --- | --- | -| Access token | A token that is used for setting up an S2 connection. It is random binary data and must be generated by a cryptographically secure pseudorandom number generator and have a minimum length of 32 bytes. It is encoded using Base64. Also see [11A. Response status 200](#11a-response-status-200). | -| Communication client | The nodes which behaves as the HTTP client when initiating an S2 connection. | -| Communication server | The nodes which behaves as the HTTP server when initiating an S2 connection. | -| End user | A person or entity that manages nodes. For the purpose of this specification it is assumed that there is already a trust relationship in place between this person and the nodes. This means that the way the trust relationship has been established is out of scope for this specification. | -| End user environment | A restricted area within an application that contains all the nodes that belong to the end user. Also see [Deployment of nodes](#deployment-of-nodes). | -| Endpoint | A service which can handle pairing requests or initiates pairing requests itself. An endpoint can represent one node, but could also represent many. | -| Initiator node | The node that takes the initiative to pair with a responder node. This is typically the node from which the user initiates the pairing process. It is the counterpart of the responder node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | -| Node | Refers to an instance of either a CEM or a RM as defined in EN 50491-12-1 and implementing this specification. S2 communication between two nodes can only be established if one of the nodes is a CEM and the other a RM. These nodes must also have the same end user. | -| Node ID | A globally unique identifier for an node in the UUID format. | -| Node ID alias | A short identifier for an node, which is unique in the context of a single endpoint. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | -| Pairing attempt | The process of pairing two nodes. The process can be completed successfully or unsuccessfully. | -| Pairing client | The endpoint which behaves as the HTTP client when pairing with an node. | -| Pairing code | The pairing code is the string of characters the end user has to copy from the responder node user interface to the initiator node user interface, in order to pair the two nodes. The pairing code consists of a pairing token and if required a node ID alias. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | -| Pairing endpoint registry | The central registry that keeps track of publicly available pairing servers. | -| Pairing server | The endpoint which behaves as the HTTP server when pairing with an node. | -| Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and an node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | -| Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | -| User interface | A user interface through which an end user can interact with an node. Interactions between the end user and the user interface must be secure, but this is out of scope for this specification. Examples of a user interface are a web interface, an app or a physical interface (HMI) on a device. | - - -# Architecture (informative) -This section explains the overall architecture and deployment options for CEM and RM instances. - -## Deployment of nodes - -This specification is concerned with connecting an instance of a CEM with an instance of a RM. Either of these instances are referred to as *nodes*, which either have the CEM *role* or the RM *role*. Obviously, it is only possible to pair an node with the CEM role to an node with the RM role. - -Nodes can be deployed locally within the LAN, or somewhere on a server in the WAN. Although their deployment doesn't significantly affect the working of these nodes, there are some key differences between these types of deployment. - -* **WAN** nodes are typically part of a large application that run on many servers and/or on some kind of cloud computing platform. A single application usually serves many users. Each user could have one or multiple nodes. This could for example be a cloud-based energy management system that can connect to many devices. It could also be a cloud environment of a device manufacturer that hosts the RM instances in the cloud. An end user could own multiple devices from this manufacturer, thus the application could host multiple RM instances for this particular user. We call a group of nodes that a single user can manage within one application an *end user environment*. It is also possible that an end user environment contains both CEM and RM instances. The user interface is typically a web interface or a smartphone app. -* **LAN** nodes are typically part of an application that runs on an embedded computer device somewhere in the building. Such a device could be a physical energy management system, an energy flexible device such as a home battery, heat pump or EV charger, or a gateway device which connects to an energy flexible device through some kind of protocol. Often an application will only host a single node, but it is also possible that an application hosts multiple nodes. A device could function completely on its own, but it cloud also be connected to an internet based application of the manufacturer. The user interface could be a physical human-machine interface on the device, but also be a smartphone app that connects directly to the device (e.g. via bluetooth), or a smartphone app or web interface that connects to an internet based application of the manufacturer. For energy flexible devices, it is assumed that they could also have no user interface at all, or that they are very constrained when it comes to computing power. It is assumed that a CEM always has a user interface. - -![Deployment_options](@site/static/img/communication-layer/deployment_options.png) - -There are three types of S2 connections between nodes possible: - -* **WAN-WAN**: A connection between two nodes deployed in a WAN. Connecting between them is straightforward and can be done based on URLs, based on DNS domain names. It is possible to rely on TLS certificates that can be validated thanks to a public key infrastructure. -* **WAN-LAN**: A connection between a LAN deployed node a WAN deployed node. Since there is almost always a firewall and/or NAT between these two, it is assumed that it is only possible to set up a connection from the LAN to the WAN; not the other way around. Connecting from the LAN node to the WAN node can be done based on a URL, and common TLS certificates can be used thanks to public key infrastructure. -* **LAN-LAN**: A connection between two LAN deployed nodes. It is assumed that in this situation we cannot rely an internet connection, making it impossible to rely on a public key infrastructure for TLS certificates. That is why for this type of connection self-signed TLS certificates are used. Connections are made based on hostnames that are resolved to IP-addresses using Multicast DNS (mDNS), since IP-addresses are not guaranteed to be stable. Discovering another node could be done using DNS Service Discovery (DNS-SD). - -## Pairing and unpairing from the perspective of the end user -The end user can take the initiative to *pair* a single CEM instance with a single RM instance. This process has to be started with one of the nodes. Which node this is depends on the deployment and implementation decisions of the node, but ideally it could be either one. The node however needs to have a user interface. We'll call the node that user uses to start the pairing process the *initiator*. We'll call the other node the *responder*. - -The first step of pairing is establishing a connection from the initiator node to the responder node. This can be done in several ways: -* Enter the responder node address manually at the initiator node. -* If the responder node is deployed in the WAN, the URL could be retrieved through a registry. The end user would have to select the type of node from a list of known node services in its region. -* If both nodes are deployed in the LAN however, nodes can be automatically be detected. The end user would have to select the node from a list of automatically discovered nodes. - -The second step is entering the pairing code of the responder node. This is a means for the end user to confirm that these two nodes are allowed to send control signals through S2 to each other. The pairing code can be obtained from the responder node. The pairing code is a (seemingly) random string of characters. This pairing code is typically displayed somewhere in the user interface of the other node. We recommend to use a dynamic token which expires after 5 minutes. However, if the node has a physical presence in the building and doesn't have user interface, there is also the option to have a static pairing code which can be printed on the device. - -Optionally, the initiator node can send a signal to the responder node to indicate that the end user has started the pairing process and has selected the responder node. This could trigger the user interface of the responder node to proactively show the pairing code (e.g. through a pop-up) to improve the user experience. - -Once the pairing code is known to the initiator node, the pairing process is started. It is established that both nodes are compatible and it is verified that the entered pairing code is correct. Pairing could either fail or succeed. - -If pairing is performed successfully, the CEM and RM instances should establish a connection with each other and communicate through S2. If the connection is interrupted, the instances will automatically try to reestablish the connection. - -Once a CEM is paired, the user has to possibility to command either of the nodes to *unpair*. After unpairing the CEM and RM instances can no longer communicate through S2 (unless the end user pairs them again). - -![Pairing_process_user](@site/static/img/communication-layer/pairing_process_user.png) - -
-Image generated using the following PlantUML code: - -``` -@startuml -participant "Initiator node" as i -participant "Initiator node UI" as iui -actor "End user" as e -participant "Responder node UI" as rui -participant "Responder node" as r - -e->iui: Provide identity of Responder node (e.g. URL) -rui->e: Retrieve pairing code -e->iui: Provide pairing code -i->r: Attempt pairing -r->i: Pairing result (success or failure) -iui->e: Pairing result (success or failure) -@enduml -``` -
- -## The node and the endpoint - -Within this protocol we make a clear distinction between two types of identities: the one of the *endpoint* and the one of the *node* itself. - -An node is an instance of a CEM or a RM. It is either a specific energy management system or service, or the representative of a physical energy flexible device, such as a heat pump or a home battery. It is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. - -The endpoint is basically the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. - -## Used technology for pairing and communication - -An S2 connection basically consists of four steps: discovery, pairing, communication and unpairing. For these steps different types of technology are used. - -On of the main technologies the process relies on is HTTP REST. All interactions based on HTTP are formally described in OpenAPI specification files. [OpenAPI](https://swagger.io/specification/) is a formal language for specifying HTTP based API's. It can be used to generate reference documentation for developers, as well as stub code for many programming languages. - -### Discovery - -The first step is finding the responder node from the initiator node. In principle this is done based on the URL of the responder node. However, to improve user experience, two systems exist to find this URL in a more user friendly manner. For more details see [Discovery](#discovery). - -* If the responder node is deployed in the WAN, the end user can find the endpoint through the pairing endpoint registry. This would result in a list of vendors that offer nodes. -* If both nodes are deployed in the LAN however, the responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. This process can also be used when an node is deployed in the WAN, but the device also has a presence in the LAN. - -### Pairing - -The pairing process itself is completely based on HTTP REST. One node behaves as the HTTP server, and the other as the HTTP client. This process is described in an OpenAPI file. The process consists of multiple steps. If the pairing process is completed successfully, the nodes will agree on an access token. This token is used to initiate communication or to unpair. - -We'll refer to the endpoint that behaves as the HTTP server during the pairing process as the *pairing server*, and the client as the *pairing client*. - -Pairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) - -### Communication - -Communication is setting up the actual session, where S2 messages are being exchanged. - -The process always starts with HTTP based communication, but then is handed over to a protocol which supports a two-way messages based communication channel. Currently the only protocol that is being used is WebSockets, but there are plans to add other options in the future. The HTTP interface is also specified in an OpenAPI file, together with the unpairing process. - -We'll refer to the endpoint that behaves as the HTTP server during the communication process as the *communication server*, and the client as the *communication client*. - -It should be noted that pairing and communication are two separate HTTP interfaces, that don't have to be used in the same way. It could be that an node is an pairing client, but then becomes a communication server. This depends on the deployment of the nodes (see [Pairing details for different deployments](#pairing-details-for-different-deployments)). - -Communication interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) - -After the HTTP interaction a WebSocket is established (other transport protocols will be added in the future). The communication server is always the WebSocket server. This server must use the same TLS certificate as the HTTP server. - -### Unpairing - -Either node can take the initiative to unpair from the other node. This is done using the same HTTP OpenAPI specification and the same HTTP server and client as the communication. The details for unpairing differ depending if it is the communication server or if it is the communication client that initiates the unpairing process. - -Unpairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) - - -## Pairing details for different deployments - -As explained, the pairing process is based on HTTP REST calls. That means that for every pairing attempt, one node behaves as the HTTP server, and one HTTP node behaves as the pairing client. The logical solution would be to make the initiator node the HTTP client and the responder node the HTTP server. After all, it is the HTTP client that takes the initiative to contact the HTTP server. The HTTP server cannot take the initiative to contact the HTTP client. - -The objective is to have all nodes be able to be the initiator node, as well as the responder node. This is necessary to provide a consistent user experience. The end user might not be aware which node is deployed in the LAN or in the WAN, and then it might be confusing that, for example, his energy management system both provides pairing codes and asks for pairing codes. - -If every node must be able to be the initiator node in certain situations, and the responder node in other situations, and the easiest solution is to implement the initiator as HTTP client and the responder as HTTP server, you might come to the conclusion that every node needs to be able to behave both as an HTTP server and as an HTTP client. - -There are however two situations where this is not possible: - -* **WAN initiator node and LAN responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that it is not possible to approach a LAN HTTP server from a WAN client. This specifications offers two approaches to this problem: - * Accept this limitation and not allow the WAN node to be the initiator node. Pairing can only be performed when the LAN node is the initiator node and the WAN node is the responder node. Special care must be taken to explain this to the end user. - * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement the pairing HTTP server in the cloud, even though the node itself is in the WAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. -* **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTP server is far more memory intensive than an HTTP client, there is an option to implement a LAN RM instance purely as an HTTP client. A long-polling mechanism is available to indicate to the HTTP Server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTP server. In other words: in this specific situation the initiator node behaves as the HTTP server, and the responder node only has to be an HTTP client. - -![Pairing_direction](@site/static/img/communication-layer/pairing_direction.png) - - -# Formal specification and versioning (normative) - -This document serves as an overall specification of the S2 Connect protocol. However, where possible, the protocol has been specified in a formal specification language in order to minimize possible interpretation and allow tooling to assist the implementation of the specification. Since many details are better described in these formal specification files, they are not described in this document. Where the formal specification files and this document overlap, the formal specification file is leading. - -| Part of specification | Description | Specification format | Location | -| --- | --- | --- | --- | -| S2 Connect pairing API | HTTP based interaction to pair two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-pairing.yml) | -| S2 Connect connection API | HTTP based interaction set up a communication channel for S2 messages between two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-connection-init.yml) | -| S2 JSON message structure | The types of S2 messages that can be exchanges between nodes | JSON schema files | [Github](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema) | - -## Versioning of OpenAPI files -The S2 Connect pairing API and the S2 Connect connection API are formally defined as an OpenAPI file. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. The pairing API and the connection API share the same version number. - -The minor version is increased when backwards compatible changes are made. Be aware that we consider adding items to certain lists of enums (e.g. the list of supported hash functions) backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. - -The major version is increased when non-backwards compatible changes are made. - -The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTP server and HTTP clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must server all version on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as port of the URL (e.g. `["v1", "v2"]`). - -> TODO: Versioning of S2 JSON Schema's needs to be explained - -## Addressing endpoints (normative) -The URL of the pairing and connection API are used in the discovery process, pairing process and connection process, as wel as the basis for TLS certificates. - -For WAN deployed endpoints, the URL **must** be based on a DNS domain name. - -For LAN deployed endpoint, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instance within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD, and is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. - -# Pairing process (normative) - -The pairing process is based on the trust relation that the end user has with both the CEM and the RM instances. That trust relation is out of scope for this specification and is up to CEM and RM providers to implement. - -For each pairing attempt, one endpoint must be the HTTP server, while the other is the HTTP client. The table below defines which endpoint is het HTTP server or client is which situation. - -| Initiator node | Responder node | Responder is exclusively RM? | Initiator HTTP role | Responder HTTP role | WAN pairing server | Remark | -| ----------------- | ----------------- | ---------------------------- | ------------------- | ------------------- | --------------------- | ------ | -| WAN | WAN | Doesn't matter | Client | Server | n/a | | -| WAN | LAN | Doesn't matter | Client | n/a | Server | The WAN pairing server for LAN deployment is optional | -| LAN | WAN | Doesn't matter | Client | Server | n/a | | -| LAN | LAN | Yes | Server | Client | n/a | Through long-polling. Decision to implement the server or the client is up to the RM endpoint. | -| LAN | LAN | Yes | Client | Server | n/a | Alternative for long-polling. Decision to implement the server or the client is up to the RM endpoint. | -| LAN | LAN | No | Client | Server | n/a | | - -> A LAN deployed RM implementation can choose if it implements the HTTP server, or that it implements the HTTP client and uses long-polling. This feature exists to accommodate RM implementations with constrained hardware. - -A CEM can be paired with multiple RM's a the same time. A RM can only be paired with one CEM at a time. An node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). - -## Discovery - -> TODO: This section still notes to be expanded - -In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing endpoint of the other node. In other words, the discovery process is a way to provide an node with the URL of another node which is needed to start the pairing process. Alternatively, it is always possible to initiate the pairing by manually providing the URL by the end user. - -> NOTE: the discovery process specification is work in progress and will be updated soon. - -### WAN-WAN -Both the S2 RM and CEM run in the cloud (for example communicating with the device via a manufacturer specific protocol). Discovery of the other node by lookup in a central registry. - -> NOTE: how the API of the registry will look like will be published soon - -### WAN-LAN - -A hybrid scenario where either the RM or CEM is deployed locally and the other in the cloud. Discovery of the cloud node by lookup in a central registry or DNS-SD in case the node is also present on the LAN. - -> NOTE: the DNS-SD service specification will be published soon - -### LAN-LAN -A LAN scenario where both RM and CEM are running on the same local network. Discovery through DNS-SD. - -> NOTE: the DNS-SD service specification will be published soon - -### DNS-SD based discovery -DNS-SD is used for automatically discover nodes from an node that is deployed in the LAN. This method can be used in three ways. - -* To discover another node that is deployed in the LAN, which is the responder node -* To advertise a [long polling endpoint](#long-polling) so other initiator nodes in the LAN could connect to this node -* To discover an node of which the RM is deployed in the WAN, but that also has a presence in the LAN. - -S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it is an HTTP based protocol. S2 Connect uses the following DNS-SD values: - -| DNS-SD property | Value for S2 | -| --- | --- | -| Service type | `_s2connect` | -| Protocol | `_tcp` | -| Port | No fixed port, decision is up to the implementation | -| Subtypes | `_cem` and `_rm` | -| Service name | Identical to the hostname (see [here](#addressing-endpoints-normative) for more details) - -An endpoint deployed in the LAN **should** publish it service through DNS-SD once it is ready for pairing, and until it shuts down. - -A device has a presence in the LAN (e.g. a heat pump which is connected to the internet via the end user's WiFi), but has its RM deployed in the WAN, can still use DNS-SD to advertise its endpoint to any local nodes (e.g. a physical energy management device containing an CEM). In this case the device doesn't have to offer any actual service in the LAN; it merely uses the TXT record to advertise its WAN endpoint URL. An arbitrary port may be specified. - -Two DNS-SD subtypes are used for endpoints. Subtypes can be used to filter services. - - * `_cem` is used when the endpoint contains one or more CEM node - * `_rm` is used when the endpoint contains one or more RM node - * `_cem` and `_rm` are both used when the endpoint contains both CEM and RM nodes - -S2 uses the following TXT records when registering for services. In the table below, M indicates a mandatory value and O indicates on optional value. - -| Record name | M/O | Description -| --- | --- | --- | -| `txtver` | M | Version of this specification of usage of the TXT record. **Must** be the literal value `1` for this version | -| `e_name` | O | The name of this endpoint (identical to the `name` property in the `EndpointDescription` object as defined in de OpenAPI specification) | -| `e_logoUrl` | O | The logoUrl of this endpoint (identical to the `logoUrl` property in the `EndpointDescription` object as defined in de OpenAPI specification) | -| `deployment` | M | **Must** be the literal value `LAN` or `WAN` (identical to the `logoUrl` property in the `EndpointDescription` object as defined in de OpenAPI specification) | -| `pairingUrl` | O | The base URL of the pairing API of this endpoint, excluding the version name but including the last slash (e.g. `https://hostname.local/pairing/`). If no value is provided, a `longpollingUrl` **must** be provided. -| `longpollingUrl` | O | The base URL of the pairing API of this endpoint on which the longpolling feature is implemented. The URL should be provided excluding the version name but including the last slash (e.g. `https://hostname.local/pairing/`). Only needs to be provided when longpolling is supported. Can only be provided if the value for `deployment` is equal to `LAN`. - -> Note: It is mandatory to provide a value for at least one of the properties `pairingUrl` and `longpollingUrl`. Providing both is also possible. - -The receiver of the service description **must** use the URL provided in the TXT records; not the hostname or IP-address and port associated with the service registry. - -> Note: You may have noticed that the full URL of the endpoint is used in the TXT records, even though the endpoint and port are already exposed by DNS-SD itself. This is done to avoid any problems with TLS certificates, which are pinned to a certain domain name. When using a WAN endpoint, the full URL needs to be specified as well, since no local service is actually being offered. - -> Scanning for endpoints could for example be done using the following [avahi](https://avahi.org/) command: -> -> `avahi-browse -r _s2connect._tcp` -> -> Registering an endpoint could for example be done using the folling avahi command: -> -> `avahi-publish-service -s "EVSE1038" _s2connect._tcp 443 "txtvers=1" "e_name=brand" "deployment=LAN" "pairingUrl=https://EVSE1038.local:443/pairing/" --sub _rm._sub._s2connect._tcp` - -## The pairing token, the node ID alias and the pairing code - -The pairing token is a random string of characters that is generated by the responder node. It is a secret which is transferred by the end user to the initiator node, and then is verified during the pairing process. Since there will be many cases where the end user has to manually type in the pairing token, the pairing token has to be short enough to make it easy for the end user to type in, but long enough to make it secure. - -The pairing token may consist out of lower case letters, upper case letters and numbers. The pairing token **must** be generated by a cryptographically secure pseudorandom number generator. The pairing token is typically dynamically generated when the user requests the pairing token at the user interface of the responder node. Dynamically generated pairing tokens **must** expire after a duration; five minutes is the recommended duration. However, energy flexible devices that do not have a user interface are allowed to have a static pairing token, that for example can be printed somewhere on the physical device. Static pairing tokens do not expire. Dynamic pairing tokens **must** contain at least 4 characters. Static paring tokens **must** contain at least 6 characters. Pairing tokens may be as long as the developer deems necessary. - -| Type of pairing token | Minimal length | Validity | Regular expression | -| --- | --- | --- | --- | -| Dynamic pairing token | 4 characters | Limited duration, 5 minutes is recommended | `^[0-9a-zA-Z]{4,}$` | -| Static pairing token | 6 characters | Indefinitely | `^[0-9a-zA-Z]{6,}$` | - -An endpoint can host multiple nodes. When attempting to pair a certain node, the endpoint needs to know exactly which of its nodes this pairing attempt is aimed at. Nodes are uniquely identified with their node ID. Since this node ID is a UUID, it is pretty long and cumbersome to type in. That is why an endpoint can assign its nodes a *node ID alias*. This is an identifier that is intended to be short, and only unique within the context of this particular endpoint. Node ID aliases could be assigned by the endpoint whenever new nodes are created, but also could be generated dynamically only when someone is attempting to pair to this node. This way, node ID aliases have a short live, and can be reused by other nodes at other moments. This allows to use shorter node ID aliases. Node ID aliases are a string of characters, which may include lower case letters, upper case letters and numbers. Node ID aliases are ideally as short as possible (at least one character), but should of course be long enough to allow the endpoint to uniquely identify an node. When an endpoint only contains one node, there is no need for a node ID alias. - -The **node ID alias** can be validated with the following regular expression: - -``` -^[0-9a-zA-Z]+$ -``` - -Although the pairing token and the node ID alias are two separate strings, which are treated completely differently in the pairing process, they are presented together to the user as one string: the *pairing code*. The pairing code is simply the node ID alias, followed by a dash ('-'), followed by the pairing token. When there is no node ID alias, the pairing code is simply identical to the pairing token. - -> Note: The node ID alias is not secret and is exchanged during a pairing attempt. The pairing token is secret. It is never exchanged; it is only used as input for the challenge response function. - -``` -When no node ID alias is used (i.e. the endpoint only contains one node): - [pairing code] = [pairing token] -When a node ID alias ID is used: - [pairing code] = [node ID alias]-[pairing token] -``` - -Alternatively, the **pairing code** can be validated with the following regular expression: - -``` -^([0-9a-zA-Z]+-)?[0-9a-zA-Z]{4,}$ -``` - -The pairing code allows us to transfer two pieces of information by only bothering the end user once. Due to its format the initiator node can easily extract the node ID alias and the pairing token from the pairing code by splitting the string at the dash. - - -## Challenge response process - -This protocol uses a two-way challenge response process to verify that both nodes have the same pairing token. For this process it doesn't matter which node has issued the pairing token and which node has the pairing token that was entered by the end user. The reason a two-way challenge response process is used to verify the pairing token is that it allows to establish trust without having to expose the pairing token. Both a challenge and a response are binary data, which are encoded using Base64. - -The challenge that is generated by the HTTP Client is called the `clientHmacChallenge`. The response to this challenge, generated by the HTTP Server, is called the `clientHmacChallengeResponse`. The challenge that is generated by the HTTP Server is called the `serverHmacChallenge`. The response to this challenge, generated by the HTTP Client, is called the `serverHmacChallengeResponse`. - -A challenge is a nonce; a random binary data. It **must** be generated by a cryptographically secure pseudorandom number generator and it **must** have a minimal length of 32 bytes. The response is calculated based on the function described below. Both the generator of the challenge and the receiver of the challenge calculate the response based several input parameters. Since both nodes should have the same input, both nodes should calculate the same response. The node that received the challenge sends it back to the node that generated the challenge. Now the node that generated the challenge simply has to check if the received response is identical to the expected response that he calculated himself. - -The algorithm to calculate the response is based on the HMAC (hash-based message authentication code) function. This function has a *key* and a *message* as arguments. Most programming languages have a function or library available that provides HMAC functions. - -The HMAC function itself uses a cryptographic hash function for its calculations. Since cryptographic hash functions might contain vulnerabilities, this protocol uses a simple selection mechanism for the cryptographic hash function. The HTTP client sends with the requestPairing HTTP request a list of supported hash functions. In the response the HTTP server indicates which hash function it has selected from this list. This function **must** be used for all response calculations during het pairing attempt. Currently there is only one hash function available (SHA256), but other options might be added in the future. - -It order to avoid man-in-the-middle attacks when using self-signed certificates, the SHA256 fingerprint of the TLS *server certificate* can also be used as input for calculating the response. - -> Note that the pairing token is encoded using Base64, so it must also be decoded using Base64 before it can be used in the challenge response function. - -The exact function to calculate the response depends on the deployment of the nodes. - -``` -When both nodes have a LAN deployment: - R = HMAC(C, T || F) - -When at least one node has a WAN deployment: - R = HMAC(C, T) -``` - -Where: -| Symbol | meaning | -| ------ | ------- | -| `R` | Response -| `HMAC` | HMAC function with the selected cryptographic hash function | -| `C` | Challenge | -| `T` | Pairing token | -| `F` | SHA256 fingerprint of the TLS server certificate of the HTTP server | -| `\|\|` | Concatenation | - - -## Pre-pairing interaction - -> This section is only applicable for LAN-LAN pairing - -> TODO: This section needs to be improved - -The user visits the S2ClientNodeUI and the S2ServerNode has been discovered (so the S2ServerNode base URL is known) by the the S2ClientNode per [discovery](#discovery) as specified above. The S2ClientNode does a preparePairing HTTP request to let the S2ServerNode know that there is an S2ClientNode that wants to pair. It is up to the S2ServerNode implementation to decide what to do with this signal. It can be used to display a pop-up with the pairing token in its UI to improve the user experience. It must be implemented by the client, but only when there is a clear distinction between the moment preparePairing is called and when requestPairing is called. When preparePairing is called, it is not guaranteed that a call to pairingRequest or cancelPreparePairing will follow so it is recommended to put a time-out on showing the pairing token in the S2ServerNodeUI. - -## Long-polling - -> This section is only applicable for LAN-LAN pairing - -The long-polling feature is intended to support endpoints that only want to implement an HTTP client, and not an HTTP server. Typically this is because the endpoint runs on constrained hardware. An endpoint is only allowed to only implement the client if it exclusively hosts nodes that have the RM role. - -Imagine we have endpoints, one only hosting a CEM node, and one only hosting a RM node. The RM runs on constrained hardware, and only implements the HTTP client. We have two situations: - -1. **The RM is the initiator node and the CEM is the responder node**: The CEM issues a pairing code, the end users enters the pairing code in the UI of the RM. The RM endpoint (HTTP client) then sends a normal HTTP request to the CEM endpoint (HTTP server) to initiate pairing. The normal pairing process can be used, and long-polling is not required. -2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTP server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTP client). - -For the second situation the long-polling feature can be used. It can be used by the initiator node (the HTTP server) to notify the responder node (the HTTP client) it wants to pair. - -> Informative: Long-polling is a technique that allows the server to send signals to the client without a significant delay, and without relying on additional technologies such as Websockets or Server-Sent Events. The common alternative is polling, where the client sends a request on a regular interval; let's say every 30 seconds. Polling creates a delay from the perspective of the server. If the server wants to send something to the client, it has to wait until the client contacts the server; which in the worst case 30 seconds. With long-polling the server doesn't immediately respond the the request (a hanging HTTP request). It responds immediately when the server wants the client to do something, or just before the request would time out. After receiving the response from the server the client immediately opens a new request to allow the server to send signals the client again. - -The long-polling feature fulfills the following functionality: -* Make the existence of the client known to server, together with the nodes IDs of the nodes that are represented by the client endpoint -* Send the `NodeDescription` and `EndpointDescription` of nodes represented by the client when requested by the server -* Send a prepare pairing signal or cancel prepare pairing from the server to the client for a particular node ID -* Send the signal from the server to the client to initiate pairing for a particular node ID -* Send an error message from the client to the server when pairing cannot be performed - -A client capable of long-polling **should** initiates long-polling when it encounters a endpoint through DNS-SD that indicates that is available for long-polling requests. When the endpoint represents zero nodes the client **cannot** attempt long-polling. When the endpoint advertisement itself, or only its long-polling indication disappears from DNS-SD the client **should** stop the long-polling process for that server. The client **must** also stop when it is no longer capable of pairing. - -The server **must** always respond within 25 seconds after receiving the request. The client **must** use a request time-out of at least 30 seconds. - -> TODO: Move the OpenAPI version selection process to its own section so we don't have to explain it every time - -The client starts the process by doing a POST request to the `/waitForPairing` path. For full normative details see the OpenAPI specification files. The request body contains a list of objects. The client **must** always provide an object for each node ID it represents. The items in the list have a mandatory property `clientNodeId` and optional parameters `clientNodeDescription`, `clientEndpointDescription`. The client should only provide values for these properties when requested by the server. The object also contains the optional property `errorMessage`, which only should be used when an error has occurred before pairing. - -When the server wants the client to immediately do a new request, it responds with status code 204. When it wants the client to do something, it responds with status 200 and a response body containing a list. This list contains an object only for node IDs represented by the client, that the server wants to do something with. This object contains the mandatory properties `clientNodeId` and `action`. The `action` property is an enumeration indicating an action the server wants to execute for a specific node. The possible action values are `sendNodeDescription`, `preparePairing`, `cancelPreparePairing` and `requestPairing`. - -The table below indicates how the client should respond to the requests of the server. Note that the server could send multiple actions (for different node IDs) in the same response. The server **cannot** provide multiple objects for the same node ID in one response. - -| Status code | Value `action` | Pairing code entered? | What should the client do | What should the client include in the next request body | -| --- | --- | --- | --- | --- | -| 204 | n/a | n/a | Only send a next request | Only the `clientNodeId` | -| 200 | `sendNodeDescription` | n/a | Only send the next request | The `clientNodeId`, `clientNodeDescription` and `clientEndpointDescription` | -| 200 | `preparePairing` | n/a | Prepare pairing for the mentioned node ID and send the next request | Only the `clientNodeId` | n/a | -| 200 | `cancelPreparePairing` | n/a | Cancel prepare pairing for the mentioned node ID and send the next request | Only the `clientNodeId` | -| 200 | `requestPairing` | Yes | Initiate the pairing for the mentioned node ID and send the next request | Only the `clientNodeId` | -| 200 | `requestPairing` | No | Initiate the pairing for the mentioned node ID and send the next request | Only the `clientNodeId`, and for the associated object provide an `errorMessage` with value `NoValidTokenOnPairingClient` | -| 400 | n/a | n/a | Stop long-polling until next time long-polling is advertised through DNS-SD | n/a | -| 401 | n/a | n/a | Stop long-polling, do not attempt long-polling with this node again | n/a | -| 500 | n/a | n/a | Wait before trying to send the next request | Only the `clientNodeId`| - -2. **The CEM is the initiator node and the RM is the responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user inters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTP server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTP client). - -When the server sends the `requestPairing` action, the node on the client must already have issued a pairing token. If the node uses a dynamic pairing code, it could be the case that the pairing code has expired, or that no pairing code has been issued in the first place. In that case the client **must** perform a new request with an `errorMessage` containing the value `NoValidTokenOnPairingClient` in the object associated with the node ID of the node should have attempted to pair. - -The activity diagram below summarizes the complete long-polling process from teh perspective of the client. - -![image](@site/static/img/communication-layer/long-polling_activity_diagram.png) - -## Pairing interaction - -The pairing process itself consists of several HTTP interactions between client and server. The image below depicts a successful pairing process between two nodes. - -![image](@site/static/img/communication-layer/pairing_http_process.png) - -
-Image generated using the following PlantUML code: - -``` -@startuml -participant "HTTP Client" as Client -participant "HTTP Server" as Server - -'select version of pairing API -Client->Server++: 1. GET / (index containing pairing API versions) -Server-->Client: 2. Response status 200 -deactivate Server -Client->Client: 3. Decide pairing version - -'compatibility check -Client->Server: 4. POST /[version]/requestPairing -activate Server -Server->Server: 5. Calculate clientHmacChallengeResponse -Server-->Client: 6. Response status 200 -deactivate Server - -Client->Client: 7. HTTP Client checks clientHmacChallengeResponse - -Note over Client: HTTP Client now trusts HTTP Server - -Client->Client: 8. Calculate serverHmacChallengeResponse - -alt Pairing server is Communication Server - Client->Server: 9A. POST /[version]/requestConnectionDetails - activate Server - Server->Server: 10A. HTTP Server checks serverHmacChallengeResponse - Note over Server: HTTP Server now trusts HTTP Client - Server-->Client: 11A. Response status 200 - deactivate Server -else Pairing server is Communication Client - Client->Server: 9B. POST /[version]/postConnectionDetails - activate Server - Server->Server: 10B. HTTP Server checks serverHmacChallengeResponse - Note over Server: HTTP Server now trusts HTTP Client - Server-->Client: 11B. Response status 204 - deactivate Server -end - -Client->Server++: 12. POST /[version]/finalizePairing -Server-->Client: 13. Response status 204 -deactivate Server - -Note over Client, Server: Pairing finalized - -@enduml -``` -
- -### 0. Precondition - -Before two node can be paired, the following preconditions must be met. - -1. The HTTP server and the HTTP client can only start with a pairing request when they are fully initialized and have all the details of the nodes it represents available. -2. The HTTP client must have the base URL of the pairing API (e.g. `https://hostname.local/pairing/`) -3. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. - -> Note: The initiator node could be the HTTP server or the HTTP client - -If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the pairing process. - -### 1. GET / (index containing pairing API versions) -Since the HTTP client does not know which major versions of the pairing API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | -| Store fingerprint of TLS certificate for later check | | - -If no checks fail the client **should** proceed to the next step. - -### 2. Response status 200 -The server responds with a list of implement major versions of the pairing API. It is formatted as a JSON array contains all the supported version of the pairing API (e.g. `["v1"]`). - -If the HTTP client does not support any of the provided versions, it means that the two endpoints are not compatible, and that pairing is not possible. - -### 3. Decide pairing version -From the provided list of major versions of the pairing API, the HTTP client must select one that is implement by the HTTP client itself (typically the highest supported version). - -### 4. POST /[version]/requestPairing -In the first POST request the client provides the server with same information about itself. The main purpose of this is to check if these two nodes are compatible. - -The client sends the following information (for full details see the OpenAPI specification file): - -| Information | Description | -| --- | --- | -| `clientNodeDescription` | Information about the node that wants to pair, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the initiator node | -| `clientEndpointDescription` | Information about the client endpoint. An important field is the deployment. | -| `nodeId` | The nodeID of the node that is being targeted (this filed can be omitted if the client only knows the `nodeIdAlias` or when the endpoint only represents one node). | -| `nodeIdAlias` | The nodeIdAlias of the node that is being targeted (this field can be omitted if the client only knows the `nodeId` or when the endpoint only represents one node) | -| `supportedCommunicationProtocols` | List of supported communications protocols of the client | -| `supportedS2MessageVersions` | List of supported S2 message versions by the client | -| `supportedHmacHashingAlgorithms` | List of supported hashing algorithms for the challenge response function (currently only `SHA256` is supported and **must** be present) | -| `clientHmacChallenge` | The challenge of the client for the challenge response process (see [Challenge response process](#challenge-response-process) | -| `forcePairing` | Indicate if the nodes must pair, even though they (currently) do not support the same S2 message versions (this could in the future be solved with a software update) | - -Be aware that the client may never provide a value for `nodeId` and `nodeIdAlias` at the same time. When the server endpoint only represents one node, both properties may be omitted. - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | -| Check if same fingerprint is used as previous request | Pairing is failed, do not proceed with the pairing attempt | - -If no checks fail the client **should** proceed to the next step. - - -The server **must** perform the checks in the table below to make sure that it can proceed with this request. If one of these checks fail, the server should respond with an HTTP status 400 and a `PairingResponseErrorMessage`. The contents of the `additionalInfo` field is supposed the be helpful and up to the implementer. - -| Check | Type of `PairingResponseErrorMessage` when check fails | Can be ignored when `forcePairing` is true ?| -| --- | --- | --- | -| Is the request properly formatted and does it follow the schema? | `ParsingError` | No | -| Does it recognize the `nodeIdAlias`? | `NodeNotFound` | No | -| Are the endpoint and node ready for pairing? | `Other` | No | -| If no `nodeIdAlias` provided, does this endpoint indeed only represent one node? | `NoNodeIdProvided` | No | -| Does the targeted node have a different role than the initiator node (i.e. you cannot pair two RM's or two CEM's)? | `InvalidCombinationOfRoles` | No | -| Does the server accept any of the provided hashing algorithms for the challenge response process? | `IncompatibleHmacHashingAlgorithms` | No | -| Is there overlap between the communication protocols? | `IncompatibleCommunicationProtocols` | Yes | -| Is there overlap between the S2 message versions? | `IncompatibleS2MessageVersions` | Yes | -| If the targeted node on the HTTP server is the initiator node, did the end user provide a valid pairing token? | `NoValidPairingTokenOnPairingServer` | No | -| If the targeted node on the HTTP server is the responder node, does the node have a pairing token which has not expired? | `NoValidPairingTokenOnPairingServer` | No | - -> Note: If the node that is being paired is an RM which is already paired, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation must be unpaired. - -> Note: If the targeted node is already paired with the initiator node, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation is maintained. - -> Note: This is the only step where it is checked if the pairing code has expired. If the pairing token expires after this step, but during the pairing process, the pairing process will continue. A pairing attempt is limited to 15 seconds. - -If no checks fail the server **should** proceed to the next step. - -### 5. Calculate clientHmacChallengeResponse -The server selects an hashing algorithm for the challenge response function from the list that was provided by the client. This has to be a hashing algorithm that the server considers secure. The server calculates a response to the provided `clientHmacChallenge`. For details see [Challenge response process](#challenge-response-process). - -### 6. Response status 200 -In order to formulate a response, the server **must** generate a `pairingAttemptId`. This is an identifier that **must** be generated by a cryptographically secure pseudorandom number generator and encoded using Base64. This identifier is used to keep track of all the HTTP interactions during the pairing attempt, and **must** be provided by the HTTP client as a header with all subsequent interactions. A pairing attempt **must** be completed within 15 seconds, or else the server **must** assume the pairing attempt has failed. - -The server responds with the following information (for full details see the OpenAPI specification file): - -| Information | Description | -| --- | --- | -| `pairingAttemptId` | The generated identifier for this pairing attempt | -| `serverNodeDescription` | Information about the node that is being targeted, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the responder node | -| `serverEndpointDescription` | Information about the server endpoint. An important field is the deployment. | -| `selectedHmacHashingAlgorithm` | The hashing algorithm for the challenge response function as selected in step 2 | -| `clientHmacChallengeResponse` | The response to the challenge provided by the HTTP client as calculated in step 2 | -| `serverHmacChallenge` | The challenge created by the HTTP server for the challenge response process (see [Challenge response process](#challenge-response-process).) | - -The client **must** perform the following checks of this data. - -| Check | How to proceed if check fails | -| --- | --- | -| Can the contents of the response be parsed? | Do not proceed with the pairing attempt | -| Is the response formatted according to the schema? | call `/finalizePairing` where `success` is `false` if `pairingAttemptId` is available | -| Is the role of the node at the server compatible? | call `/finalizePairing` where `success` is `false` | - -If no checks fail the server **should** proceed to the next step. - -### 7. HTTP Client checks clientHmacChallengeResponse -The HTTP client checks the `clientHmacChallengeResponse` provided by the HTTP server in step 6. It does that by calculating the response itself, and checking if the results is identical to the `clientHmacChallengeResponse`. - -If the result is identical, the client **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt. It **must** attempt to inform the HTTP server of this by doing an HTTP request to `finalizePairing` where the value of `success` must be `false`. - -Note that in case of a local server, the TLS certificate fingerprint is part of the challenge. So if the challenge succeeds, the certificate fingerprint is correct, and the certificate can be trusted. The client **must** pin this certificate, and trust this certificate for future use. - - -### 8. Calculate serverHmacChallengeResponse -The HTTP client calculates a response to the provided `serverHmacChallenge` using the hashing algorithm as indicated in the `selectedHmacHashingAlgorithm`. For details see [Challenge response process](#challenge-response-process). - -From hereon the process branches into two scenario's, depending on if the HTTP client will be the communication client or the communication server. See [Mapping the CEM and RM to communication server or client](#mapping-the-cem-and-rm-to-communication-server-or-client) for which node will perform which role for communication. - -If the HTTP server will be the communication *server* steps 9A, 10A and 11A **should** follow. If the HTTP server will be the communications *client* steps 9B, 10B en 11B **should** follow. - -### 9A. POST /[version]/requestConnectionDetails -> Note: The `pairingAttemptId` must be provided through a header for this HTTP request - -The HTTP client makes a request for the connection details. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 8. - -If the `pairingAttemptId` is not recognized by the server (or has expired), the server **must** respond with status code 401. - -If the request was not understood by the server for any other reason, the server **must** respond with status 400. - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | -| Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | - -If no checks fail the client **should** proceed to the next step. - - -### 10A. HTTP Server checks serverHmacChallengeResponse -The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 9A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. - -If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 or step 4. - -### 11A. Response status 200 -The server **must** generates an access token for the HTTP client. The access token is random binary data and **must** be generated by a cryptographically secure pseudorandom number generator and **must** have a minimum length of 32 bytes. It is encoded using Base64. The access token **cannot** be used by the initiator node until the pairing process is completed. - -The server responds with two pieces of information: - -| Information | Description | -| --- | --- | -| `initiateConnectionUrl` | The base URL for the connection process (does not include the version number) | -| `accessToken` | The access token that was generated for this node | - -If the response is understood and properly formatted, the HTTP client **should** proceed to the next step. Otherwise the HTTP client **must** stop the pairing attempt. It **must** attempt to inform the HTTP server of this by doing an HTTP request to `finalizePairing` where the value of `success` must be `false`. - -### 9B. POST /[version]/postConnectionDetails -> Note: The `pairingAttemptId` must be provided through a header for this HTTP request - -The HTTP sends the connection details to the HTTP server. This request also serves as a way to send the HTTP server the `serverHmacChallengeResponse` calculated in step 8. - -In this case the pairing server will become the communication client. Once the pairing server becomes the communication client, it does not know what the certificate that the communication server will use. That is why it needs to provide it using the property `certificateFingerprint`. This property is a map, where the key of the map is the hashing algorithm used to generate the fingerprint, and the value is the fingerprint itself. The hashing function `SHA256` and the related fingerprint **must** always be provided. - -| Information | Description | -| --- | --- | -| `serverHmacChallengeResponse` | The response for the challenge response process | -| `initiateConnectionUrl` | The base URI for the connection process (does not include the version number) | -| `accessToken` | The access token that was generated for this node | -| `certificateFingerprint` | A map with the fingerprint of the CA (root) certificate. The key of the map is the name of the hashing algorithm used to generate the fingerprint, the value is the fingerprint itself. The key `SHA256` must always be provided. | - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | -| Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | - -If no checks fail the client **should** proceed to the next step. - -The server **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| `pairingAttemptId` is recognized | Respond with status code 401 | -| Request could not be parsed correctly | Respond with status code 400 | - -If no checks fail the server **should** proceed to the next step. - -### 10B. HTTP Server checks serverHmacChallengeResponse -The HTTP server checks the `serverHmacChallengeResponse` provided by the HTTP client in step 9A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. - -If the result is identical, the server **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTP client anymore. If the HTTP client wants to make another attempt, it **must** start again at step 1 or step 4. - -### 11B. Response status 204 -The server confirms it has accepted the response and received the connection details by responding with HTTP status 204. - -### 12. POST /[version]/finalizePairing -> Note: The `pairingAttemptId` must be provided through a header for this HTTP request - -If all interaction has been successful until this point, the HTTP client **must** do a request to finalize the pairing attempt. The provided value for `success` **must** be `true`. - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Pairing is failed, do not proceed with the pairing attempt | -| If self-signed TLS certificate, check if server is local | Pairing is failed, do not proceed with the pairing attempt | -| Check if TLS certificate is pinned | Pairing is failed, do not proceed with the pairing attempt | - -If no checks fail the client **should** proceed to the next step. - -The server **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| The `pairingAttemptId` is correctly recognized | respond with status code 401 | -| The request is not understood for any other reason | respond with status code 400 | - -If no checks fail the server **should** proceed to the next step. - -Receiving a `/finalizePairing` request marks the completion of the pairing attempt for the HTTP server. If the HTTP server issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTP client. - -### 13. Response status 204 -To confirm the successful completion of the pairing attempt, the HTTP server responds to the client with HTTP status code 204. This response marks the completion of the pairing attempt for the HTTP client. If the HTTP client issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTP client. - -If the HTTP server was using a self-signed TLS certificate, the HTTP client can now store the self-signed root certificate. The client **must** check that this is the CA certificate that is used for all future interaction with this endpoint. The HTTP server is allowed to use a new self-signed server certificate, as long as it is signed by the self-signed CA certificate that was used during the pairing process. - -### Interruption of the process -A pairing attempt has a maximum duration of 15 seconds. That means that once a `pairingAttemptId` has been issued, this `pairingAttemptId` cannot be used after 15 seconds since it was issued. From the perspective of the HTTP server, any pairing attempt that is not completed in 15 seconds (with success or not) is considered a failed attempt. From the perspective of the HTTP client, if the server does not respond within 15 seconds since it received the `pairingAttemptId`, it must consider the pairing attempt as failed. If the HTTP client wants to make another attempt, it should start again at step 1 or step 4. - -### Invalid interactions -If the server receives a wrong HTTP request (e.g. `/postConnectionDetails` while it was expecting `/requestConnectionDetails`) or when it receives the requests in the wrong order (e.g. `/finalizePairing` with `success` = `true` before calling `/requestConnectionDetails`) it **must** respond with a status 400 and consider the pairing attempt as failed. The only exception is receiving the same request twice. - -# S2 Connection (normative) - -After two nodes have been paired, the nodes exchange S2 messages over a secure connection. - -The following mechanism **must** be used to initiate a secure connection between two nodes. Client authentication is based on a one-time use communication token that needs to be renewed every time a new S2 session is created. The communication client will always attempt to set up an S2 connecting with the communication server when there is no connection. For more details see [Reconnection strategy](#reconnection-strategy). - -## Mapping the CEM and RM to communication server or client - -The CEM and RM roles defined by the S2 protocol are distinct from the server and client roles of the pairing process. The following rules apply to determine whether the RM or CEM acts as a client or server for the communication initialization. - -* If a connection is set up between a WAN node and a LAN node, the WAN node must act as a communication server, and the local node must act as a communication client. -* If a connection is set up between two nodes that are similarly deployed (i.e. both in WAN, or both in LAN), the CEM must act as a communication server, and the RM must act as a communication client. - -There are four scenarios for CEM and RM deployment, and applying the rules above yields the following: - -| CEM deployment | RM deployment | CEM acts as | RM acts as | -|----------------|---------------|-------------|------------| -| WAN | WAN | Communication server | Communication client | -| WAN | LAN | Communication server | Communication client | -| LAN | WAN | Communication client | Communication server | -| LAN | LAN | communication server | Communication client | - -> Note: A device developed solely for use as an RM in a LAN setup will never function as a communication server. - -## Connection initiation - -During the pairing process an `accessToken` is generated by the node which will be the communication server and sent to the node that will be the communication client. This `accessToken` can be used by the communication client to set up a session with the communication server for exchanging S2 messages. The `accessToken` does not expire, but it can only be used (successfully) once to set up a connection. Each time a new connection is made, the `accessToken` will be renewed. The communication server will generate a new `accessToken` and sends it to the communication client. Since this `accessToken` is the only means to connect two nodes once they are paired, the connection initiation process makes sure that both nodes confirm that they have successfully persisted the new `accessToken` before invalidating the old `accessToken`. - -![connection initiation](@site/static/img/communication-layer/connection-initiation.png) - -
-Image generated using the following PlantUML code: - -``` -@startuml -participant "HTTP Client" as Client -participant "HTTP Server" as Server - -Client->Server++: 1. GET / (index containing pairing API versions) -Server-->Client: 2. Response status 200 -deactivate Server -Client->Client: 3. Decide pairing version - -Client->Server++: 4. POST /[version]/initiateConnection -Server->Server: 5. Generate new pending accessToken -Server-->Client--: 6. Response status 200 -Client->Client: 7. Store pending accessToken -Client->Server++: 8. POST /[version]/confirmAccessToken -Server->Server: 9. Activate new accessToken for this node ID -Server-->Client--: 10. Response status 200 -Client -> Client : 11. Remove old accessToken -@enduml -``` - -
- -### 0. Precondition - -Before an node can initiate a connection, it needs three things. - -1. The HTTP server and the HTTP client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. -2. The HTTP client must have the base URL of the connection API (e.g. `https://hostname.local/connection/`) -3. The two nodes must have been paired successfully and must have an accessToken for this pairing - -If the HTTP client does not fulfill these preconditions, it **cannot** send the first HTTP request of the connection process. - -### 1. GET / (index containing communication API versions) -Since the HTTP client does not know which major versions of the communication API are implemented by the server, it must first do a GET request to the index (e.g. `https://hostname.local/pairing/`). - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Do not proceed with connection, try again later | -| If self-signed TLS certificate, check if server is local | Do not proceed with connection, try again later | - -If no checks fail the client **should** proceed to the next step. - -### 2. Response status 200 -The server responds with a list of implement major versions of the pairing API. It is formatted as a JSON array contains all the supported version of the pairing API (e.g. `["v1"]`). - -If the HTTP client does not support any of the provided versions, it means that the two endpoints are not compatible, and that connection is not possible. - -### 3. Decide communication API version -From the provided list of major versions of the communication API, the HTTP client must select one that is implement by the HTTP client itself (typically the highest supported version). - -### 4. POST /[version]/initiateConnection - -Since there are situations in which the client cannot know for sure which `accessToken` the communication server uses for this pairing, the communication client must keep a persisted list of `accessTokens` (which will typically contain only one `accessToken`). - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Initiation is failed, do not proceed with the initiation attempt | -| If self-signed TLS certificate, check if server is local | Initiation is failed, do not proceed with the initiation attempt | -| Check if TLS certificate is pinned | Initiation is failed, do not proceed with the initiation attempt | - -If no checks fail the client **should** proceed to the next step. - -The client sends the following information (for full details see the OpenAPI specification file). In addition, the `accessToken` is sent through a header. - -| Information | Description | -| --- | --- | -| `clientNodeId` | The node ID of the communications client that wants to connect to the server. | -| `serverNodeId` | The node ID of the communications server that the client wants to connect to. | -| `clientNodeDescription` | Information about the node, such as brand, logo and type. This only needs to be provided if the communication client wants to update this information, otherwise the communication server will assumer the stored information is still valid. | -| `clientEndpointDescription` | Information about the client endpoint. This only needs to be provided if the communication client wants to update this information, otherwise the communication server will assumer the stored information is still valid. | -| `supportedCommunicationProtocols` | List of supported communications protocols of the client | -| `supportedS2MessageVersions` | List of supported S2 message versions by the client | - -The server **must** perform the checks in the table below to make sure that it can proceed with this request. If one of these checks fail, the server should respond with an HTTP status 400 and a `CommunicationDetailsErrorMessage` or with HTTP status 401. The contents of the `additionalInfo` field of the `CommunicationDetailsErrorMessage` is supposed the be helpful and up to the implementer. - -| Check | Response | What should the client do with this message? | -| --- | --- | --- | -| Is the request properly formatted and does it follow the schema? | `CommunicationDetailsErrorMessage` with errorMessage `ParsingError` | Retry later | -| Was this node ID paired with this node, but was it unpaired? | `CommunicationDetailsErrorMessage` with errorMessage `NoLongerPaired` | Do not retry, inform end user | -| Is this `clientNodeId` paired with the `serverNodeId`? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | -| Is the `serverNodeId` known? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | -| Is this the correct `accessToken` for this node ID? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | -| Is there overlap between the communication protocols? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleCommunicationProtocols` | Retry later | -| Is there overlap between the S2 message versions? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleS2MessageVersions` | Retry later | -| Are the endpoint and node ready for pairing? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | - -### 5. Generate new pending `accessToken` - -For each paired node the server saves an active `accessToken`. In addition to that, the server also has a list for pending `accessToken`s, that were generated but not yet confirmed by the client. This list contains entries, each consisting of an `accessToken`, the node IDs of the client and server nodes and a timestamp. - -The server generates a new `accessToken` and saves this together with the node ID and the current time as in entry in the list of pending tokens. The `accessToken` **must** be generated by a cryptographically secure pseudorandom number generator. - -### 6. Response status 200 - -In the request the client supplied a list of supported communication protocols and S2 messages versions. The server must select one of the options that were provided by the client. - -The server sends the following information (for full details see the OpenAPI specification file). - -| Information | Description | -| --- | --- | -| `selectedCommunicationProtocol` | The communication protocol that was selected by the server | -| `selectedS2MessageVersion` | The S2 message version that was selected by the server | -| `accessToken` | The newly generated pending `accessToken` | -| `serverNodeDescription` | Information about the node at the server, such as brand, logo and type. This only needs to be provided if the communication server wants to update this information, otherwise the communication client will assumer the stored information is still valid. | -| `serverEndpointDescription` | Information about the server endpoint. This only needs to be provided if the communication server wants to update this information, otherwise the communication client will assumer the stored information is still valid. | - -The client **must** perform the checks in the table below to make sure that it can proceed with this request. - -| Check | What should the client do? | -| --- | --- | -| Is the request properly formatted and does it follow the schema? | Do not proceed and try again later with step 1 | -| Was the selected S2 message version offered in the request? | Do not proceed and try again later with step 1 | -| Was the selected communication protocol offered in the request? | Do not proceed and try again later with step 1 | - -### 7. Store pending accessToken -It client adds the pending `accessToken` to its list of `accessTokens`, but does not yet remove the old one. If the client is not able to persist the pending `accessToken` (e.g. because the storage device or the DBMS is not available), the client does not proceed with the process. Once the client is able to persist `accessTokens` again, it can retry to set up a connection starting with step 1. - -### 8. POST /[version]/confirmAccessToken -The client confirms to the server that it has successfully persisted the pending `accessToken`. The **pending** `accessToken` is provided through the header of the request. - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Do not proceed with connection, try again later | -| If self-signed TLS certificate, check if server is local | Do not proceed with connection, try again later | -| Check if TLS certificate is pinned | Do not proceed with connection, try again later | - -If no checks fail the client **should** proceed. - -### 9. Activate new `accessToken` for this node ID - -If the provided `accessToken` is in the list pending `accessToken`s, and the token was generated not more than **15 seconds** ago, the server now makes the pending `accessToken` the active `accessToken` for this pairing of nodes (thereby invalidating the old `accessToken`). Also, the entry is removed from the list of pending `accessToken`s. - -If the provided `accessToken` is not in the list of pending `accessTokens`s, the server must not accept the connection and respond with status 401. The client can try again later starting at step 1. - -If the server is not able to active the new `accessToken` (e.g. because the storage device or the DBMS is not available), the server must not accept the connection and responds with an error code 500. The client can try again later starting at step 1. - -### 10. Response status 200 - -The communication server sends the details and credentials to open a socket for communicating the S2 messages. The exact contents of this message depend on the selected communication protocol. In any case it will be a JSON object containing the field `communicationProtocol`. The presence on other fields will depend on the value of the `communicationProtocol` field. - -If the response is not understood by the communication client, the client **should** retry later. - -### 11. Remove old accessToken - -Step 10 functions as a confirmation to the communication client that the communication server has activated the new `accessToken` for this pairing. The old `accessToken` cannot be used anymore, so the communication client must remove the old `accessToken` from the list of `accessToken`s. - -### Interruption of the process -Once the communication server has generated a new pending `accessToken`, it must be confirmed within 15 seconds by the communication client. If this doesn't happen, a client will have to start the process from step 1 (or step 4) again. - -If the communication client doesn't receive a response to confirming the new `accessToken` (step 10), it does not know if the server has activated the new `accessToken`, or if the old `accessToken` is still in place. It now has (at least) two `accessToken`s in its list, and does not know for certain which one is activate at the communication server. It should try all the accessTokens sequentially. If it finds an `accessToken` that is accepted by the communication server, it can remove the other `accessTokens`. - -## WebSocket based communication - -This section specifies how to use WebSocket Secure as the S2-over-TCP/IP application layer protocol. - -The WebSocket client **must** run on the communication client and the WebSocket server on the communication server. - -The choice for a WebSocket as application layer communication protocol has the advantage that the session concept is intrinsically introduced with the communication protocol. All S2 communication happens in the context of a (stateful) S2 session which is catered for by the WebSocket session. So, the S2 session matches the WebSocket session. - -The client **must** perform the following checks during this request: - -| Check | How to proceed if check fails | -| --- | --- | -| Check TLS certificate | Websocket connection failed, do not proceed with the connection attempt | -| If self-signed TLS certificate, check if server is local | Websocket connection failed, do not proceed with the connection attempt | -| Check if TLS certificate is pinned | Websocket connection failed, do not proceed with the connection attempt | - -If no checks fail the client **should** proceed to the next step. - - -### Authentication -For each S2 WebSocket session the client **must** authenticate itself using the commToken in the authorization header of the websocket connection request, following [RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://datatracker.ietf.org/doc/html/rfc6750). - -### Encrypted connection (WSS) - -Communication over the WebSocket endpoint **must** be encrypted following [RFC 6455 The WebSocket Protocol](https://datatracker.ietf.org/doc/html/rfc6455). S2 **MUST NOT** be sent over unencrypted channels. Therefore a wss connection (the URL starting with wss://) must be used. - -### Compression - -The WebSocket Protocol ([RFC6455](https://datatracker.ietf.org/doc/html/rfc6455)) has an extension for compression: [**RFC 7692**](https://datatracker.ietf.org/doc/html/rfc7692.html) implementing so called per-message-deflate compression. https://datatracker.ietf.org/doc/html/rfc7692 - -RFC 7692 is widely supported by WebSocket libraries and and we are exchanging JSON plain text messages, it is expected to save a large amount of data. Therefore, implementations of S2 WebSockets **SHOULD** support RFC 7692 and **SHOULD** enable it whenever possible. - -### Keepalive & heartbeat (ping / pong) - -WebSockets by default have a **keepalive** and a **heartbeat mechanism**. Keepalive is designed to keep the connection open while heartbeat is designed to check the latency and check the connection is still working. This means that periodically a ping frame is sent to the server (endpoint) and in response a pong frame is sent. - -In order to reduce network traffic, S2 WebSocket implementations **SHOULD** not send ping frames more often than every 50 seconds. Ping & pong frames are control frames and **MAY** include payload of maximum 125 bytes. - -[https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2%5C%5C) - - - -### Termination - -An S2 session can be terminated in different ways: - -* In case an node unexpectedly becomes unavailable, the WebSocket connection **CAN** timeout. This will cause an S2 session to be terminated. More details about the timeout can be found [in the heartbeat section](#keepalive--heartbeat-ping--pong) -* an node **CAN** terminate the S2 session by sending the S2 terminate message, including an optional earliest time that the session can be restored. The other node can take this into account in planning and (in the case of a client) deciding when to attempt to reconnect. -* After two nodes have unpaired, the S2 WebSocket connection **MUST** be terminated immediately. - -### Reconnection strategy - -Once an S2 session is terminated it cannot be resumed and if further communication is required, a new session needs to be started. an S2 client node may try to establish a WebSocket connection. - -An exponential back-off strategy **SHOULD** be used, increasing the time between reconnection attempts at every failed attempt. If a reconnection time was included in any termination, S2 client nodes are permitted to make an attempt to reconnect before this time. At the specified time the delay between reconnection attempts **SHOULD** be reset. - -## Communication - JSON messages - -The S2 standard has been encoded into a JSON schema specification, for details see: [S2 JSON](https://github.com/flexiblepower/s2-ws-json) - -### State of communication - -![State of Communication](@site/static/img/communication-layer/state-of-communication.png) - -
-Image generated using the following PlantUML code: - -``` -@startuml - -hide empty description -[*] --> WebSocketConnected - -WebSocketConnected : ResourceManagerDetails, PowerMeasurement, \n PowerForecast can be exchanged - -WebSocketConnected --> ControlTypeActivated: Activate ControlType -ControlTypeActivated --> WebSocketConnected: Deactivate ControlType - -ControlTypeActivated: In addition to messages in\nthe "Initialized" state,\nControlType specific message\ncan now be exchanged as well - -WebSocketConnected --> WebSocketDisconnected: Termination of Session -ControlTypeActivated --> WebSocketDisconnected: Termination of Session - -WebSocketDisconnected --> [*] -@enduml -``` -
- -| State | Messages that can be sent by CEM /received by RM | Messages that can be sent by RM / received by CEM | -| --- | --- | --- | -| WebSocket Connected | SelectControlType
SessionRequest
ReceptionStatus | ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | -| ControlType PEBC activated | PEBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | PEBC.EnergyConstraint
PEBC.PowerConstraint
RevokeObject InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | -| ControlType PPBC activated | PPBC.EndInterruptionInstruction
PPBC.ScheduleInstruction
PPBC.StartInterruptionInstruction
SelectControlType
SessionRequest
ReceptionStatus | PPBC.PowerProfileDefinition
PPBC.PowerPorfileStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | -| ControlType OMBC activated | OMBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | OMBC.Status
OMBC.SystemDescription
OMBC.TimerStatus RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | -| ControlType FRBC activated | FRBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | FRBC.ActuatorStatus
FRBC.FillLevelTargetProfile
FRBC.LeakageBehaviour
FRBC.StorageStatus
FRBC.SystemDescription
FRBC.UsageForecast
FRBC.TimerStatus
RevokeObject
InstructionStatusUpdate ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | -| ControlType DDBC activated | DDBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | DDBC.ActuatorStatus
DDBC.AverageDemandRateForecast
DDBC.SystemDescription
DDBC.TimerStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement PowerForecast
SessionRequest
ReceptionStatus | - -# Unpairing process (normative) - -Unpairing can be initiated by either node, and **should** only be done when instructed by the end user. The node that did not take the initiative to unpair **should** try to inform the end user that the node is no longer paired. - -## Unpairing by the communication client - -If the communication client takes the initiative to unpair, it first **should** close the communication session regarding this pairing (if there is one). Then it **must** call the `/[version]/unpair` HTTP API endpoint of the server using its `accessToken` (after discovering the API version at the server, the same way as steps 1 to 3 as mentioned in [Connection initiation](#connection-initiation)). Upon receiving the unpair call from a client, the server node **must** remove all security information related to this pairing. Then the client **must** remove all security information of the communication server related to this pairing. - -## Unpairing by the communication server - -If the server takes the initiative to unpair, first it **must** remove all security information related to client node. After that, if there is an active S2 session, it **should** send an S2 [SessionRequest](/model-reference/Common/SessionRequest/) message with type [RECONNECT](/model-reference/Common/SessionRequestType/) to the client. The next `/[version]/initiateConnection` HTTP API request **must** fail with the `No longer paired` response to let the client know that it is not paired anymore. - -Client and server **can** keep other (non-security) information for, for example, user experience purposes. - -# Security (normative) - -> TODO: This section needs to be expanded to explain measures against ddos - -## Brute-force protection -To prevent brute-force pairing request, the server **MUST** implement rate limiting on the requestPairing endpoint. It is up to the server implementation to define the type of rate limiting. - - -## TLS Certificates - -All HTTP and WebSocket communication uses TCP over TLS with server certificates. The server certificates **MUST** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. - -There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local domain name. This is also the only situation where self-signed certificates are allowed. - -The following image shows the difference. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. - -![image.png](../../static/img/communication-layer/certificate-chains.png) - -
-Image generated using the following PlantUML code: - -``` -@startuml -struct PublicRootCA -struct PublicIntermediateCA -struct PublicServerCertificate - -PublicRootCA --> PublicIntermediateCA -PublicIntermediateCA --> PublicServerCertificate - - -struct SelfSignedCA -struct LocalServerCertificate - -SelfSignedCA --> SelfSignedCA -SelfSignedCA --> LocalServerCertificate -@enduml -``` -
- - -### Trusting a self-signed root certificate - -The self-signed root certificate is by default not trusted. However during the pairing phase, the server with the self-signed root certificate will share the fingerprint of the certificate during the pairing phase as part of the challenge. This will enable the client to verify the self-signed root certificate, and create trust. In this case, the [pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code) **MUST** include the first 9 bytes, encodes as 12 base64 encoded characters, of the fingerprint of this self-signed CA certificate and the client **MUST** check this fingerprint. From this moment on, the client will store the complete fingerprint of the self-signed root certificate, and use it to verify the server certificate for all future connections. - -Note that the `preparePairing` and `cancelPreparePairing` endpoints can be called before the pairing has happened. So in the case the server is running on a LAN (and thus uses self-signed certificates), the client can skip the certificate validation steps on those endpoint. This means that the HTTP client **must** be configured to accept self-signed certificates during the pairing process. Since the pairing process consists of several HTTP requests, the HTTP client **must** check that for every request the same self-signed certificate is used by the HTTP server. If this is not the case, the HTTP client **cannot** proceed with the request. - - -### Updating the certificates - -A server can update its certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. - -If the server is in local-local mode, and uses a self-signed CA certificate, the CA certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server. If the used crypto for the the CA certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its server certificate at least once every 6 months. - - -## Cipher suites - -Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in [Accepted crypto algorithms](./accepted-crypto.md). This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. From afa03e2c0ef139d2b782704fd90cc439c52fed50 Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 9 Jul 2026 21:39:15 +0200 Subject: [PATCH 62/67] Move all headings in S2 Connect one level deeper --- .../discovery-pairing-authentication.md | 182 +++++++++--------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 6883851..4ac05cb 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -3,11 +3,11 @@ title: S2 Connect Specification sidebar_position: 2 --- -# S2 Connect - Specification + Version: v1.0.0 -# Introduction +## Introduction S2 Connect is a JSON-based protocol specification implementing the EN50491-12-2 "S2" standard for home and building energy management. @@ -15,7 +15,7 @@ This specification addresses everything needed to create a secure and interopera The protocol is designed to specify communication between two devices, a resource (e.g. a heat pump or EV charger) and a (home) energy management system. It is worth noting that, while this specification focuses on describing the interaction between two components, a (home) energy management is likely to be communicating with multiple resources at the same time. -# Version +## Version This version of this specification is based on the following versions of the underlying formal specification files (see [Formal specification and versioning (normative)](#formal-specification-and-versioning-normative) for more details). @@ -26,7 +26,7 @@ This version of this specification is based on the following versions of the und > Note: S2 Connect is not directly linked to the version of S2 JSON. The exact version of S2 JSON that is being used by the CEM and RM is negotiated during session initiation. -# List of abbreviations +## List of abbreviations |Abbreviation | Meaning |---|---| @@ -44,9 +44,9 @@ This version of this specification is based on the following versions of the und | UUID | Universally Unique IDentifier (see [RFC 9562](https://www.rfc-editor.org/rfc/rfc9562)) | | WAN | Wide Area Network (i.e. the public internet) | -# Background (informative) +## Background (informative) -## Context +### Context S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other over IP networks. The general architecture is described in the S2 architecture standard introducing CEM and RM functions are defined in EN 50491-12-1:2018, in which also the S2 interface is defined between CEM and RM. S2 Connect builds upon two other projects: @@ -56,7 +56,7 @@ S2 Connect builds upon two other projects: Both the S2 Standard and S2 JSON can be used independently from S2 Connect. -## Requirements +### Requirements S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other. The CEM and RM are logical concepts within the S2 architecture, therefore, the S2 standard does not make any assumptions on how and where the CEM and RM are deployed in a real-life situation. In practice, the CEM could be deployed on a local gateway in a LAN or as a server somewhere on the internet (WAN), while the RM could be part of the device itself, deployed on an add-on module or on the internet as well. S2 Connect provides a single solution which can be used between devices connected through a LAN, on the internet, or a combination of those. S2 Connect implements the following high-level requirements: @@ -82,7 +82,7 @@ The entire trust model of S2 Connect is based on the fact that there is already >> "Why doesn't S2 Connect use oAuth?" is a common question. oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. There is a way to make it work, but since it is such a non-typical way the decision was made to not use oAuth 2.0 for pairing. For more details see here. -# Terms and definitions (normative) +## Terms and definitions (normative) This specification uses the concepts that are defined below. @@ -110,10 +110,10 @@ This specification uses the concepts that are defined below. | User interface | A user interface through which an end user can interact with a node. Interactions between the end user and the user interface must be secure, but this is out of scope for this specification. Examples of a user interface are a web interface, an app or a physical interface (HMI) on a device. | -# Architecture (informative) +## Architecture (informative) This section explains the overall architecture and deployment options for CEM and RM instances. -## Deployment of nodes +### Deployment of nodes This specification is concerned with connecting an instance of a CEM with an instance of a RM. Either of these instances are referred to as *nodes*, which either have the CEM *role* or the RM *role*. Obviously, it is only possible to pair a node with the CEM role to a node with the RM role. @@ -130,7 +130,7 @@ There are three types of S2 connections between nodes possible: * **WAN-LAN**: A connection between a LAN deployed node a WAN deployed node. Since there is almost always a firewall and/or NAT between these two, it is assumed that it is only possible to set up a connection from the LAN to the WAN; not the other way around. Connecting from the LAN node to the WAN node can be done based on a URL, and common TLS certificates can be used thanks to public key infrastructure. * **LAN-LAN**: A connection between two LAN deployed nodes. It is assumed that in this situation an internet connection might not be available, making it impossible to rely on a public key infrastructure for TLS certificates. That is why for this type of connection self-signed TLS certificates are used. Connections are made based on hostnames that are resolved to IP-addresses using Multicast DNS (mDNS), since IP-addresses are not guaranteed to be stable. Discovering another node could be done using DNS Service Discovery (DNS-SD). -## Pairing and unpairing from the perspective of the end user +### Pairing and unpairing from the perspective of the end user The end user can take the initiative to *pair* a single CEM instance with a single RM instance. This process has to be started with one of the nodes. Which node this is depends on the deployment and implementation decisions of the node, but ideally it could be either one. The node however needs to have a user interface. The node that a user uses to start the pairing process is called the **Initiator node**. The other node receiving the pairing request is called the **Responder node**. The first step of pairing is establishing a connection from the Initiator node to the Responder node. This can be done in several ways: @@ -173,7 +173,7 @@ iui->e: Pairing result (success or failure) ```
-## The node and the endpoint +### The node and the endpoint Within this protocol a clear distinction is made between two types of identities: the one of the **Endpoint** and the one of the **Node** itself. @@ -181,20 +181,20 @@ A **Node** is an instance of a CEM or a RM. A CEM instance typically belongs to The **Endpoint** is the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host thousands of nodes. It could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. -## Used technology for pairing and communication +### Used technology for pairing and communication An S2 connection consists of four steps: discovery, pairing, communication and unpairing. For these steps different types of technology are used. One of the main technologies the process relies on is HTTPS REST. All interactions based on HTTPS are formally described in OpenAPI specification files. [OpenAPI](https://swagger.io/specification/) is a formal language for specifying HTTP(S) based API's. It can be used to generate reference documentation for developers, as well as stub code for many programming languages. -### Discovery +#### Discovery The first step is finding the Responder node from the Initiator node. In principle this is done based on the pairing URL of the Responder node. However, to improve user experience, two systems exist to find the pairing URL in a more user friendly manner. For more details see [Discovery](#discovery). * If the Responder node is deployed in the WAN, the end user can find the endpoint through the pairing endpoint registry. This would result in a list of vendors that offer nodes. * If both nodes are deployed in the LAN however, the Responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. -### Pairing +#### Pairing The pairing process itself is completely based on HTTPS REST. One node behaves as the HTTPS server, and the other as the HTTPS client. This process is described in an OpenAPI file. The process consists of multiple steps. If the pairing process is completed successfully, the nodes will agree on an access token. This token is used to initiate communication or to unpair. @@ -202,7 +202,7 @@ The endpoint that behaves as the HTTPS server during the pairing process is defi Pairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) -### Communication +#### Communication A communication process is used to set up the actual session that enables the exchange of S2 messages. @@ -216,14 +216,14 @@ Communication interaction is always TLS based (i.e. HTTPS is used). For WAN depl After the HTTPS interaction communication with the selected communication protocol is established (WebSocket, other transport protocols such as MQTT will be added in the future). For WebSocket communication, the communication server is always the WebSocket server. This server must use the same TLS certificate as the HTTPS server. -### Unpairing +#### Unpairing Either node can take the initiative to unpair from the other node. This is done using the same HTTPS OpenAPI specification and the same HTTPS server and client as the communication. The details for unpairing differ depending if it is the communication server or if it is the communication client that initiates the unpairing process. Unpairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) -## Pairing details for different deployments +### Pairing details for different deployments As explained, the pairing process is based on HTTPS REST calls. That means that for every pairing attempt, one node behaves as the HTTPS server, and one node behaves as the HTTPS client. The logical solution would be to make the Initiator node the HTTPS client and the Responder node the HTTPS server. After all, it is the HTTPS client that takes the initiative to contact the HTTPS server. The HTTPS server cannot take the initiative to contact the HTTPS client. @@ -242,7 +242,7 @@ There are however two situations where this is not possible: **Figure:** Overview of all possible ways to pair two nodes -# Formal specification and versioning (normative) +## Formal specification and versioning (normative) This document serves as an overall specification of the S2 Connect protocol. However, where possible, the protocol has been specified in a formal specification language in order to minimize possibilities for different interpretations and allow tooling to assist the implementation of the specification. Since many details are better described in these formal specification files, they are not described in this document. **Where the formal specification files and this document overlap, the formal specification file takes precedence.** @@ -253,7 +253,7 @@ This document serves as an overall specification of the S2 Connect protocol. How | S2 Connect WAN pairing endpoint registry API | HTTPS based interface to query the registry | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-wan-endpoint-registry.yml) | | S2 JSON message structure | The types of S2 messages that can be exchanges between nodes | JSON schema files | [Github](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema) | -## Versioning of OpenAPI files +### Versioning of OpenAPI files The pairing API, the session initiation API and the WAN pairing endpoint registry API are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. All S2 Connect OpenAPI files share the same version number. The minor version is increased when backwards compatible changes are made. Be aware that adding items to certain lists of enums (e.g. the list of supported hash functions) is considered backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. @@ -262,10 +262,10 @@ The major version is increased when non-backwards compatible changes are made. The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTPS server and HTTPS clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must serve all versions on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as part of the URL (e.g. `["v1", "v2"]`). -## Versioning of JSON Schema files +### Versioning of JSON Schema files JSON Schema uses its own versioning scheme, which is based on an `major.minor.patch` scheme. When negotiating the S2 JSON version number, the exact version string **must** be used (e.g. `v1.0.0`). -## Addressing endpoints +### Addressing endpoints The URL of the pairing and session initiation API are used in the discovery process, pairing process and session initiation process, as wel as the basis for TLS certificates. For **WAN** deployed endpoints, the URL **must** be based on a DNS domain name. @@ -274,7 +274,7 @@ For **LAN** deployed endpoints, the URL **must** be based on an mDNS alias or ho Also see [Pairing URL](#pairing-url). -## Selecting the version of REST APIs +### Selecting the version of REST APIs As explained in the section [Versioning of OpenAPI files](#version) the pairing server, the session initiation server and the WAN pairing endpoint registry can implement multiple versions of the API specification in parallel. As a result, the client **must** always first determine which version of the API it will use, before it can start interacting with the API. The image below depicts the interactions between client and server for the process to determine the API version that will be used. @@ -298,7 +298,7 @@ Client->Client: 3. Decide pairing version ```
-### 1. GET on the Pairing URL +#### 1. GET on the Pairing URL Since the HTTPS client does not know which major versions of the pairing API are implemented by the server, it must first perform a GET request to the [Pairing URL](#pairing-url) (e.g. `https://hostname.local/pairing/`). The client **must** perform the following checks during this request: @@ -311,20 +311,20 @@ If the root certificate is self-signed and there is no pinned certificate for th If no checks fail the client **should** proceed to the next step. -### 2. Response status 200 +#### 2. Response status 200 The server responds with a list of implemented major versions of the pairing API. It is formatted as a JSON array containing all the supported version of the pairing API (e.g. `["v1"]`). If the HTTPS client does not support any of the provided versions, it means that the two endpoints are not compatible, and that further interaction is not possible. -### 3. Decide pairing version +#### 3. Decide pairing version From the provided list of major versions of the pairing API, the HTTPS client **must** select one that is implemented by the HTTPS client itself (typically the highest supported version). The client **should** select the most recent version. -## HTTPS usage +### HTTPS usage All HTTP-based interaction in S2 Connect is strictly based on HTTPS (i.e., HTTP over TLS). HTTP (without TLS) **may not** be used for S2 Connect, and it is **recommended** to not run an HTTP (without TLS) server. HTTPS clients and servers **must** support HTTP version 1.1. It is **recommended** to support more recent version of HTTP as well. -# Pairing process (normative) +## Pairing process (normative) The pairing process is based on the trust relation that the end user has with both the CEM and the RM instances. That trust relation is out of scope for this specification and is up to CEM and RM providers to implement. @@ -343,20 +343,20 @@ For each pairing attempt, one endpoint must be the HTTPS server, while the other A CEM can be paired with multiple RMs at the same time. A RM can only be paired with one CEM at a time. A node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). -## Pairing URL +### Pairing URL The start of each pairing related interaction is the Initiator node contacting the Responder node via the *pairing URL*. Although discovery provides an option to retrieve this URL in a user friendly manner, entering the pairing URL manually **must** always be an option. Therefore, every Responder node **should** display its pairing URL somewhere (e.g. in its UI), and every Initiator node **should** have an option to pair based on a pairing URL which is manually entered by the end user. The pairing URL is the base URL of the pairing API of an endpoint. It **must** include the protocol (`https://`), it **must not** include the version of the API, but it **must** include a trailing slash (e.g. `https://hostname.local/pairing/`). For information about the domain name used in the URL see [Addressing endpoints](#addressing-endpoints). -## Discovery +### Discovery In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing URL of the other node. In other words, the discovery process is a way to provide a node with the pairing URL of another node which is needed to start the pairing process. Alternatively, it should always be possible to initiate the pairing by manually providing the URL by the end user (see [Pairing URL](#pairing-url)). There are two mechanisms for discovery: For discovering WAN endpoints there is a central online registry. For discovering endpoints within the same LAN, DNS-SD is used. -### WAN pairing endpoint registry +#### WAN pairing endpoint registry > Note: At this point the registry is specified, but not yet publicly available @@ -391,7 +391,7 @@ In addition, the number of responses can be limited. An offset can also be provi > Note: Since the pairing code also contains an identifier for the node within the endpoint, the combination of pairing code and endpoint URL is sufficient information to start a pairing attempt. -### DNS-SD based discovery +#### DNS-SD based discovery DNS-SD is used to automatically discover nodes from a node that is deployed in the LAN. This method can be used in two ways. * To discover another node that is deployed in the LAN, which is the Responder node @@ -439,7 +439,7 @@ The receiver of the service description **must** use the URL provided in the TXT > > `avahi-publish-service -s "EVSE1038" _s2connect._tcp 443 "txtvers=1" "e_name=brand" "pairingUrl=https://EVSE1038.local:443/pairing/" --sub _rm._sub._s2connect._tcp` -## The pairing token, the node ID alias and the pairing code +### The pairing token, the node ID alias and the pairing code The pairing token is a random string of characters that is generated by the Responder node. It is a secret which is transferred by the end user to the Initiator node, and then is verified during the pairing process. Since there will be many cases where the end user has to manually type in the pairing token, the pairing token has to be short enough to make it easy for the end user to type in, but long enough to make it secure. @@ -478,7 +478,7 @@ Alternatively, the **pairing code** can be validated with the following regular The pairing code allows us to transfer two pieces of information by only bothering the end user once. Due to its format the Initiator node can easily extract the node ID alias and the pairing token from the pairing code by splitting the string at the dash. -## Challenge response process +### Challenge response process This protocol uses a two-way challenge response process to verify that both nodes have the same pairing token. For this process it doesn't matter which node has issued the pairing token and which node has the pairing token that was entered by the end user. The reason a two-way challenge response process is used to verify the pairing token is that it allows to establish trust without having to expose the pairing token. Both a challenge and a response are binary data, which are encoded using Base64. @@ -515,7 +515,7 @@ Where: | `D` | Binary data | The domain name of the HTTPS server, including subdomains, without protocol or trailing slashes (e.g. `pairing.s2.example.com`) | | `\|\|` | Function | Concatenation function | -## LAN-LAN only interactions +### LAN-LAN only interactions The are several REST operations that are only to be implemented by LAN endpoints, and that can only be used by other LAN endpoints. They are described in this section. @@ -525,7 +525,7 @@ Since these operations are only intended for endpoints within the same LAN, the > Note: There are some network configurations imaginable where it would be desirable to pair two LAN nodes that are not in the same subnet. In that case automatic discovery via DNS-SD will not work, but pairing by manually entering the pairing URL and pairing code can still be used in those cases. -### Getting endpoint information +#### Getting endpoint information > This section is only applicable for LAN-LAN pairing @@ -540,7 +540,7 @@ Before the HTTPS client can start interaction with the server, it must first sel Note that there is no authentication for these operations. It is assumed that these operations are only exposed within the LAN. -### Pre-pairing interaction +#### Pre-pairing interaction > This section is only applicable for LAN-LAN pairing @@ -552,7 +552,7 @@ Note that there is no authentication for these operations. It is assumed that th Before sending signals the HTTPS client **must** have selected the version of the pairing API that will be used (see [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis)). -#### Sending the prepare pairing signal +##### Sending the prepare pairing signal The client can send the prepare pairing signal to the server by sending an HTTPS POST request to the path `/preparePairing`. The client must perform the following checks before sending information: @@ -580,7 +580,7 @@ The server **must** perform the checks in the table below. For the checks with H If no checks fail the server **should** respond with HTTP status code 204. -#### Cancelling the prepare pairing signal +##### Cancelling the prepare pairing signal If the client sent a prepare pairing signal the the server, and the end user has indicated in some way that it is no longer indented to pair with the node, it **should** send a cancel prepare pairing signal. It can do that by sending an HTTPS POST request to the path `/cancelPreparePairing`. The client must perform the following checks before sending information: @@ -597,7 +597,7 @@ The client **must** send the following information in the request. For full norm The server **should** respond with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). However, if the request originated from outside the subnet the server **must** respond with status 401. -### Long-polling for constrained endpoints in the LAN +#### Long-polling for constrained endpoints in the LAN > This section is only applicable for LAN-LAN pairing @@ -653,7 +653,7 @@ The activity diagram below summarizes the complete long-polling process from the ![image](./img/long-polling_activity_diagram.png) -## Pairing interaction +### Pairing interaction The pairing process itself consists of several HTTPS interactions between client and server. The image below depicts a successful pairing process between two nodes. @@ -706,7 +706,7 @@ Note over Client, Server: Pairing finalized ```
-### 0. Precondition +#### 0. Precondition Before two node can be paired, the following preconditions must be met. @@ -719,7 +719,7 @@ Before two node can be paired, the following preconditions must be met. If the HTTPS client does not fulfill these preconditions, it **cannot** send the first HTTPS request of the pairing process. -### 1. POST /[version]/requestPairing +#### 1. POST /[version]/requestPairing In the first POST request the client provides the server with same information about itself. The main purpose of this is to check if these two nodes are compatible. The client sends the following information (for full details see the OpenAPI specification file): @@ -772,12 +772,12 @@ The server **must** perform the checks in the table below to make sure that it c If no checks fail the server **should** proceed to the next step. -### 2. Calculate clientHmacChallengeResponse +#### 2. Calculate clientHmacChallengeResponse The server selects an hashing algorithm for the challenge response function from the list that was provided by the client. This has to be a hashing algorithm that the server considers secure. The server calculates a response to the provided `clientHmacChallenge`. For details see [Challenge response process](#challenge-response-process). To mitigate brute-force attacks, the server **must** enforce a mandatory delay of one second before sending its response to the client (step 3). For any given node at the server, pairing attempts **must** be handled sequentially, such that each second only one pairing attempt can be processed for a node. Pairing attempts targeting different nodes **may** be processed in parallel. This way, a server representing multiple nodes is not globally limited to one pairing attempt per second, but instead enforces the one-second rate limit independently per node. -### 3. Response status 200 +#### 3. Response status 200 In order to formulate a response, the server **must** generate a `pairingAttemptId`. This is an identifier that **must** be generated by a cryptographically secure pseudorandom number generator and encoded using Base64. This identifier is used to keep track of all the HTTPS interactions during the pairing attempt, and **must** be provided by the HTTPS client as a header with all subsequent interactions. A pairing attempt **must** be completed within 15 seconds, or else the server **must** assume the pairing attempt has failed. The server responds with the following information (for full details see the OpenAPI specification file): @@ -801,21 +801,21 @@ The client **must** perform the following checks of this data. If no checks fail the server **should** proceed to the next step. -### 4. HTTPS Client checks clientHmacChallengeResponse +#### 4. HTTPS Client checks clientHmacChallengeResponse The HTTPS client checks the `clientHmacChallengeResponse` provided by the HTTPS server in step 3. It does that by calculating the response itself, and checking if the results is identical to the `clientHmacChallengeResponse`. If the result is identical, the client **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt. It **must** attempt to inform the HTTPS server of this by doing an HTTPS request to `finalizePairing` where the value of `success` must be `false`. Note that in case of a local server, the TLS certificate fingerprint is part of the challenge. So if the challenge succeeds, the certificate fingerprint is correct, and the certificate can be trusted. The client **must** pin the self-signed CA (root) certificate, and trust this certificate for the remainder of the pairing relation. -### 5. Calculate serverHmacChallengeResponse +#### 5. Calculate serverHmacChallengeResponse The HTTPS client calculates a response to the provided `serverHmacChallenge` using the hashing algorithm as indicated in the `selectedHmacHashingAlgorithm`. For details see [Challenge response process](#challenge-response-process). From hereon the process branches into two scenarios, depending on if the HTTPS client will be the communication client or the communication server. See [Mapping the CEM and RM to communication server or client](#mapping-the-cem-and-rm-to-communication-server-or-client) for which node will perform which role for communication. If the HTTPS server will be the communication *server* steps 6A, 7A and 8A **should** follow. If the HTTPS server will be the communications *client* steps 6B, 7B en 8B **should** follow. -### 6A. POST /[version]/requestConnectionDetails +#### 6A. POST /[version]/requestConnectionDetails > Note: The `pairingAttemptId` must be provided through a header for this HTTPS request The HTTPS client makes a request for the connection details. This request also serves as a way to send the HTTPS server the `serverHmacChallengeResponse` calculated in step 5. @@ -833,12 +833,12 @@ The client **must** perform the following checks during this request: If no checks fail the client **should** proceed to the next step. -### 7A. HTTPS server checks serverHmacChallengeResponse +#### 7A. HTTPS server checks serverHmacChallengeResponse The HTTPS server checks the `serverHmacChallengeResponse` provided by the HTTPS client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTPS client anymore. If the HTTPS client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). -### 8A. Response status 200 +#### 8A. Response status 200 The server **must** generates an access token for the HTTPS client. The access token is random binary data and **must** be generated by a cryptographically secure pseudorandom number generator and **must** have a minimum length of 32 bytes. It is encoded using Base64. The access token **cannot** be used by the Initiator node until the pairing process is completed. The server responds with two pieces of information: @@ -850,7 +850,7 @@ The server responds with two pieces of information: If the response is understood and properly formatted, the HTTPS client **should** proceed to the next step. Otherwise the HTTPS client **must** stop the pairing attempt. It **must** attempt to inform the HTTPS server of this by doing an HTTPS request to `finalizePairing` where the value of `success` must be `false`. -### 6B. POST /[version]/postConnectionDetails +#### 6B. POST /[version]/postConnectionDetails > Note: The `pairingAttemptId` must be provided through a header for this HTTPS request The HTTPS client sends the connection details to the HTTPS server. This request also serves as a way to send the HTTPS server the `serverHmacChallengeResponse` calculated in step 5. @@ -881,15 +881,15 @@ The server **must** perform the following checks during this request: If no checks fail the server **should** proceed to the next step. -### 7B. HTTPS server checks serverHmacChallengeResponse +#### 7B. HTTPS server checks serverHmacChallengeResponse The HTTPS server checks the `serverHmacChallengeResponse` provided by the HTTPS client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. If the result is identical, the server **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTPS client anymore. If the HTTPS client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). -### 8B. Response status 204 +#### 8B. Response status 204 The server confirms it has accepted the response and received the connection details by responding with HTTP status 204. -### 9. POST /[version]/finalizePairing +#### 9. POST /[version]/finalizePairing > Note: The `pairingAttemptId` must be provided through a header for this HTTPS request If all interaction has been successful until this point, the HTTPS client **must** do a request to finalize the pairing attempt. The provided value for `success` **must** be `true`. @@ -913,24 +913,24 @@ If no checks fail the server **should** proceed to the next step. Receiving a `/finalizePairing` request marks the completion of the pairing attempt for the HTTPS server. If the HTTPS server issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTPS client. -### 10. Response status 204 +#### 10. Response status 204 To confirm the successful completion of the pairing attempt, the HTTPS server responds to the client with HTTP status code 204. This response marks the completion of the pairing attempt for the HTTPS client. If the HTTPS client issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTPS client. If the HTTPS server was using a self-signed TLS certificate, the HTTPS client can now store the self-signed root certificate. The client **must** check that this is the CA certificate that is used for all future interaction with this endpoint. The HTTPS server is allowed to use a new self-signed server certificate, as long as it is signed by the self-signed CA certificate that was used during the pairing process. -### Interruption of the process +#### Interruption of the process A pairing attempt has a maximum duration of 15 seconds. That means that once a `pairingAttemptId` has been issued, this `pairingAttemptId` cannot be used after 15 seconds since it was issued. From the perspective of the HTTPS server, any pairing attempt that is not completed in 15 seconds (with success or not) is considered a failed attempt. From the perspective of the HTTPS client, if the server does not respond within 15 seconds since it received the `pairingAttemptId`, it must consider the pairing attempt as failed. If the HTTPS client wants to make another attempt, it should start again at step 1 (starting with the API version selection process is also allowed). -### Invalid interactions +#### Invalid interactions If the server receives a wrong HTTPS request (e.g. `/postConnectionDetails` while it was expecting `/requestConnectionDetails`) or when it receives the requests in the wrong order (e.g. `/finalizePairing` with `success` = `true` before calling `/requestConnectionDetails`) it **must** respond with a status 400 and consider the pairing attempt as failed. The only exception is receiving the same request twice. -# S2 Connection (normative) +## S2 Connection (normative) After two nodes have been paired, the nodes exchange S2 messages over a secure connection. The following mechanism **must** be used to initiate a secure connection between two nodes. Client authentication is based on a one-time use communication token that needs to be renewed every time a new S2 session is created. The communication client will always attempt to set up an S2 connection with the communication server when there is no connection. For more details see [Reconnection strategy](#reconnection-strategy). -## Mapping the CEM and RM to communication server or client +### Mapping the CEM and RM to communication server or client The CEM and RM roles defined by the S2 protocol are distinct from the server and client roles of the pairing process. The following rules apply to determine whether the RM or CEM acts as a client or server for the communication initialization. @@ -948,7 +948,7 @@ There are four scenarios for CEM and RM deployment, and applying the rules above > Note: A device developed solely for use as an RM in a LAN setup will never function as a communication server. -## Session initiation +### Session initiation During the pairing process an `accessToken` is generated by the node which will be the communication server and sent to the node that will be the communication client. This `accessToken` can be used by the communication client to set up a session with the communication server for exchanging S2 messages. The `accessToken` does not expire, but it can only be used (successfully) once to set up a session. Each time a new session is made, the `accessToken` will be renewed. The communication server will generate a new `accessToken` and sends it to the communication client. Since this `accessToken` is the only means to connect two nodes once they are paired, the session initiation process makes sure that both nodes confirm that they have successfully persisted the new `accessToken` before invalidating the old `accessToken`. @@ -975,7 +975,7 @@ Client -> Client : 8. Remove old accessToken
-### 0. Precondition +#### 0. Precondition Before a node can initiate a session, it needs four things. @@ -986,7 +986,7 @@ Before a node can initiate a session, it needs four things. If the HTTPS client does not fulfill these preconditions, it **cannot** send the first HTTPS request of the session initiation process. -### 1. POST /[version]/initiateSession +#### 1. POST /[version]/initiateSession Since there are situations in which the client cannot know for sure which `accessToken` the communication server uses for this pairing, the communication client must keep a persisted list of `accessTokens` (which will typically contain only one `accessToken`). The client **must** perform the following checks during this request: @@ -1021,13 +1021,13 @@ The server **must** perform the checks in the table below to make sure that it c | Is there overlap between the S2 message versions? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleS2MessageVersions` | Retry later | | Are the endpoint and node ready for connecting? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | -### 2. Generate new pending `accessToken` +#### 2. Generate new pending `accessToken` For each paired node the server saves an active `accessToken`. In addition to that, the server also has a list for pending `accessToken`s, that were generated but not yet confirmed by the client. This list contains entries, each consisting of an `accessToken`, the node IDs of the client and server nodes and a timestamp. The server generates a new `accessToken` and saves this together with the node ID and the current time as in entry in the list of pending tokens. The `accessToken` **must** be generated by a cryptographically secure pseudorandom number generator. -### 3. Response status 200 +#### 3. Response status 200 In the request the client supplied a list of supported communication protocols and S2 messages versions. The server must select one of the options that were provided by the client. @@ -1049,10 +1049,10 @@ The client **must** perform the checks in the table below to make sure that it c | Was the selected S2 message version offered in the request? | Do not proceed and try again later with step 1 | | Was the selected communication protocol offered in the request? | Do not proceed and try again later with step 1 | -### 4. Store pending accessToken +#### 4. Store pending accessToken The client adds the pending `accessToken` to its list of `accessTokens`, but does not yet remove the old one. If the client is not able to persist the pending `accessToken` (e.g. because the storage device or the DBMS is not available), the client does not proceed with the process. Once the client is able to persist `accessTokens` again, it can retry to set up a session starting with step 1. -### 5. POST /[version]/confirmAccessToken +#### 5. POST /[version]/confirmAccessToken The client confirms to the server that it has successfully persisted the pending `accessToken`. The **pending** `accessToken` is provided through the header of the request. The client **must** perform the following checks during this request: @@ -1063,7 +1063,7 @@ The client **must** perform the following checks during this request: If no checks fail the client **should** proceed. -### 6. Activate new `accessToken` for this node ID +#### 6. Activate new `accessToken` for this node ID If the provided `accessToken` is in the list pending `accessToken`s, and the token was generated not more than **15 seconds** ago, the server now makes the pending `accessToken` the active `accessToken` for this pairing of nodes (thereby invalidating the old `accessToken`). Also, the entry is removed from the list of pending `accessToken`s. @@ -1071,22 +1071,22 @@ If the provided `accessToken` is not in the list of pending `accessTokens`s, the If the server is not able to active the new `accessToken` (e.g. because the storage device or the DBMS is not available), the server must not accept the session and responds with an error code 500. The client can try again later starting at step 1. -### 7. Response status 200 +#### 7. Response status 200 The communication server sends the details and credentials to open a socket for communicating the S2 messages. The exact contents of this message depend on the selected communication protocol. In any case it will be a JSON object containing the field `communicationProtocol`. The presence on other fields will depend on the value of the `communicationProtocol` field. If the response is not understood by the communication client, the client **should** retry later. -### 8. Remove old accessToken +#### 8. Remove old accessToken Step 7 functions as a confirmation to the communication client that the communication server has activated the new `accessToken` for this pairing. The old `accessToken` cannot be used anymore, so the communication client must remove the old `accessToken` from the list of `accessToken`s. -### Interruption of the process +#### Interruption of the process Once the communication server has generated a new pending `accessToken`, it must be confirmed within 15 seconds by the communication client. If this doesn't happen, a client will have to start the process from step 1. If the communication client doesn't receive a response to confirming the new `accessToken` (step 7), it does not know if the server has activated the new `accessToken`, or if the old `accessToken` is still in place. It now has (at least) two `accessToken`s in its list, and does not know for certain which one is activate at the communication server. It should try all the accessTokens sequentially. If it finds an `accessToken` that is accepted by the communication server, it can remove the other `accessTokens`. -## Reconnection strategy +### Reconnection strategy After session initiation, the actual S2 communication starts via the selected transport protocol. At some point this session will terminate. Unless the reason for termination is that the nodes have been unpaired, the communication client **must** try to reconnect with the communication server. In this case the client **must** always start with the session initiation process (it is not allowed to reconnect using the transport protocol that was selected last time). An exponential back-off strategy for reconnecting **must** be used, increasing the time between reconnection attempts at every failed attempt. It is recommended to use the following strategy for calculating the delay for the nth attempt to reconnect. The delay time starts when a failed connecting attempt is finished. @@ -1101,7 +1101,7 @@ Where: | `base_delay` | 2 seconds | | `max_delay` | 600 seconds | -## WebSocket based communication +### WebSocket based communication This section specifies how to use WebSocket Secure as the S2-over-TCP/IP application layer protocol. @@ -1117,30 +1117,30 @@ The client **must** perform the following checks during this request: If no checks fail the client **should** proceed to the next step. -### Opening the connection +#### Opening the connection Opening the WebSocket connection is performed by sending a GET request to the URL as provided during the connecting initiation process together with an upgrade request, as specified by [RFC6455](https://datatracker.ietf.org/doc/html/rfc6455). When using HTTP/2, also refer to [RFC8441](https://datatracker.ietf.org/doc/html/rfc8441). -### Authentication +#### Authentication For each S2 WebSocket session the client **must** authenticate itself using the commToken in the authorization header of the websocket connection request, following [RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://datatracker.ietf.org/doc/html/rfc6750). -### Encrypted connection (WSS) +#### Encrypted connection (WSS) Communication over the WebSocket endpoint **must** be encrypted following [RFC 6455 The WebSocket Protocol](https://datatracker.ietf.org/doc/html/rfc6455). S2 **MUST NOT** be sent over unencrypted channels. Therefore a wss connection (the URL starting with wss://) must be used. -### Compression +#### Compression The WebSocket Protocol ([RFC6455](https://datatracker.ietf.org/doc/html/rfc6455)) has an extension for compression: [**RFC 7692**](https://datatracker.ietf.org/doc/html/rfc7692.html) implementing so called per-message-deflate compression. https://datatracker.ietf.org/doc/html/rfc7692 The RFC 7692 compression extension is widely supported by WebSocket libraries and since JSON is exchanged as plain text messages, it is expected to save a large amount of data. Therefore, implementations of S2 WebSockets **SHOULD** support RFC 7692 and **SHOULD** enable it whenever possible. -### Keepalive & heartbeat (ping / pong) +#### Keepalive & heartbeat (ping / pong) WebSockets by default have a **keepalive** and a **heartbeat mechanism**. Keepalive is designed to keep the connection open while heartbeat is designed to check the latency and check the connection is still working. This means that periodically a ping frame is sent to the server (endpoint) and in response a pong frame is sent. S2 WebSockets implementations **should** send a ping frame every 30 seconds, and **must not** wait more than 60 seconds between sending ping frames. Ping and pong frames **may** include a payload. For more details see [RFC6455 Section 5.5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2%5C%5C). -### Termination +#### Termination An S2 session can be terminated in different ways: @@ -1148,13 +1148,13 @@ An S2 session can be terminated in different ways: * a node **CAN** terminate the S2 session by sending the S2 terminate message, including an optional earliest time that the session can be restored. The other node can take this into account in planning and (in the case of a client) deciding when to attempt to reconnect. * After two nodes have unpaired, the S2 WebSocket connection **MUST** be terminated immediately. -## Communication - JSON messages +### Communication - JSON messages S2 Connect uses [S2 JSON](https://github.com/flexiblepower/s2-ws-json) for its JSON schema specification. When using S2 Connect, the `Handshake` and `HandshakeResponse` messages (as defined by S2 JSON) **can not** be sent. They are redundant by the pairing and session initiation process. -### State of communication +#### State of communication ![State of Communication](./img/state-of-communication.png) @@ -1193,29 +1193,29 @@ WebSocketDisconnected --> [*] | ControlType FRBC activated | FRBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | FRBC.ActuatorStatus
FRBC.FillLevelTargetProfile
FRBC.LeakageBehaviour
FRBC.StorageStatus
FRBC.SystemDescription
FRBC.UsageForecast
FRBC.TimerStatus
RevokeObject
InstructionStatusUpdate ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | | ControlType DDBC activated | DDBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | DDBC.ActuatorStatus
DDBC.AverageDemandRateForecast
DDBC.SystemDescription
DDBC.TimerStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement PowerForecast
SessionRequest
ReceptionStatus | -# Unpairing process (normative) +## Unpairing process (normative) Unpairing can be initiated by either node, and **should** only be done when instructed by the end user. The node that did not take the initiative to unpair **should** try to inform the end user that the node is no longer paired. -## Unpairing by the communication client +### Unpairing by the communication client If the communication client takes the initiative to unpair, it first **should** close the communication session regarding this pairing (if there is one). Then it **must** call the `/[version]/unpair` HTTPS API endpoint of the server using its `accessToken` (after discovering the API version at the server, the same way as steps 1 to 3 as mentioned in [Session initiation](#session-initiation)). Upon receiving the unpair call from a client, the server node **must** remove all security information related to this pairing. Then the client **must** remove all security information of the communication server related to this pairing. -## Unpairing by the communication server +### Unpairing by the communication server If the server takes the initiative to unpair, first it **must** remove all security information related to client node. After that, if there is an active S2 session, it **should** send an S2 [SessionRequest](/model-reference/Common/SessionRequest/) message with type [RECONNECT](/model-reference/Common/SessionRequestType/) to the client. The next `/[version]/initiateConnection` HTTPS API request **must** fail with the `No longer paired` response to let the client know that it is not paired anymore. Client and server **can** keep other (non-security) information for, for example, user experience purposes. -# Security (normative) +## Security (normative) -## Brute-force protection +### Brute-force protection To prevent brute-force pairing request, the server **must** implement rate limiting on the requestPairing endpoint. For more details see [2. Calculate clientHmacChallengeResponse](#2-calculate-clienthmacchallengeresponse). -## DDoS countermeasures +### DDoS countermeasures Especially WAN endpoints are vulnerable for DDoS attacks. It is recommended for WAN endpoints to take countermeasures against these attacks. The nature of these countermeasures are outside of the scope of this specification. -## TLS Certificates +### TLS Certificates All HTTP and WebSocket communication uses TCP over TLS with server certificates. The server certificates **must** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local mDNS domain name. This is also the only situation where self-signed certificates are allowed. @@ -1247,7 +1247,7 @@ SelfSignedCA --> LocalServerCertificate
-### Trusting a self-signed root certificate +#### Trusting a self-signed root certificate LAN deployed nodes will have a self-signed root certificate, and a server (leaf) certificate which is signed by the self-signed root certificate. The `endpoint`, `nodes`, `preparePairing` and `cancelPreparePairing` operations can be called before the pairing has happened. For these operations the client **must** accept the self-signed certificates, even though it cannot trust the root certificate. @@ -1256,12 +1256,12 @@ Also when attempting pairing (the `requestPairing` operation) the client **must* When performing session initiation and unpairing the communication client **must** validate that server certificate and check that the certificate was signed by the self-signed root certificate that was pinned in the previous step. -### Updating the certificates +#### Updating the certificates A server can update its leaf certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. If the server is deployed in the LAN, and thus uses a self-signed root certificate, the root certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server (or the device that hosts the server). If the used crypto for the the CA (root) certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its leaf certificate at least once every 6 months. -### Certificate validation +#### Certificate validation Certificate checks are mentioned several times in this specification. The check consists of these parts: - Authenticity: In case of a WAN server, is the certificate issued by a trusted CA (using the chain of trust)? For a LAN server, is the root certificate pinned to the server’s domain name? - Domain name validation: has the certificate been issued for the (local) domain name of the server? @@ -1271,6 +1271,6 @@ Certificate checks are mentioned several times in this specification. The check With the exemption of some cases where the authenticity cannot be verified yet, all checks must be performed every time a TLS connection is set up. -## Cipher suites +### Cipher suites Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in Accepted crypto algorithms. This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. From e0599e80e2e4807fb02f148372e9013cc64677bd Mon Sep 17 00:00:00 2001 From: Jorrit Nutma Date: Thu, 9 Jul 2026 22:09:44 +0200 Subject: [PATCH 63/67] pin version 1.0.0 --- website/docusaurus.config.ts | 12 +- .../discovery-pairing-authentication.md | 6 +- .../discovery-pairing-authentication.md | 1272 +++++++++++++++++ .../img/api_version_selection.png | Bin 0 -> 5345 bytes .../version-1.0.0/img/certificate-chains.png | Bin 0 -> 15981 bytes .../version-1.0.0/img/deployment_options.png | Bin 0 -> 82997 bytes .../img/long-polling_activity_diagram.drawio | 209 +++ .../img/long-polling_activity_diagram.png | Bin 0 -> 49717 bytes .../version-1.0.0/img/pairing_direction.png | Bin 0 -> 77621 bytes .../img/pairing_http_process.png | Bin 0 -> 29541 bytes .../img/pairing_process_user.png | Bin 0 -> 11890 bytes .../img/powerpoint_img_source.pptx | Bin 0 -> 204588 bytes .../version-1.0.0/img/session-initiation.png | Bin 0 -> 11500 bytes .../img/state-of-communication.png | Bin 0 -> 27746 bytes website/s2c_versions.json | 2 +- 15 files changed, 1493 insertions(+), 8 deletions(-) create mode 100644 website/s2c_versioned_docs/version-1.0.0/discovery-pairing-authentication.md create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/api_version_selection.png create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/certificate-chains.png create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/deployment_options.png create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/long-polling_activity_diagram.drawio create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/long-polling_activity_diagram.png create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/pairing_direction.png create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/pairing_http_process.png create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/pairing_process_user.png create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/powerpoint_img_source.pptx create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/session-initiation.png create mode 100644 website/s2c_versioned_docs/version-1.0.0/img/state-of-communication.png diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 3dd4c98..96720e8 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -64,7 +64,15 @@ const config: Config = { path: "s2-connect", routeBasePath: "s2-connect", breadcrumbs: false, - sidebarPath: false + sidebarPath: false, + includeCurrentVersion:false, + // Only the latest version should be provided here because we use that trick to have the version number of the latest version in the URL (which is non default) + versions: { + "1.0.0": { + path: '1.0.0', + badge:true + }, + }, }], ['@docusaurus/plugin-client-redirects', { redirects: [ @@ -109,7 +117,7 @@ const config: Config = { type: 'docsVersionDropdown', position: 'right', // props passed to the themed navbar item component - our themed override will read `showOn` - versions: ['current', '1.0-beta2'], + versions: ['1.0.0'], docsPluginId: 's2c', // only show the dropdown when path starts with this prefix }, diff --git a/website/s2-connect/discovery-pairing-authentication.md b/website/s2-connect/discovery-pairing-authentication.md index 4ac05cb..6707964 100644 --- a/website/s2-connect/discovery-pairing-authentication.md +++ b/website/s2-connect/discovery-pairing-authentication.md @@ -1,12 +1,8 @@ --- title: S2 Connect Specification -sidebar_position: 2 --- - -Version: v1.0.0 - ## Introduction S2 Connect is a JSON-based protocol specification implementing the EN50491-12-2 "S2" standard for home and building energy management. @@ -80,7 +76,7 @@ Non-repudiation is explicitly not guaranteed by this protocol. The entire trust model of S2 Connect is based on the fact that there is already a trust relation between the end user and the CEM and RM. If the CEM and RM do not use adequate security mechanisms, it might be possible to attack the system. ->> "Why doesn't S2 Connect use oAuth?" is a common question. oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. There is a way to make it work, but since it is such a non-typical way the decision was made to not use oAuth 2.0 for pairing. For more details see here. +> "Why doesn't S2 Connect use oAuth?" is a common question. oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. There is a way to make it work, but since it is such a non-typical way the decision was made to not use oAuth 2.0 for pairing. For more details see here. ## Terms and definitions (normative) diff --git a/website/s2c_versioned_docs/version-1.0.0/discovery-pairing-authentication.md b/website/s2c_versioned_docs/version-1.0.0/discovery-pairing-authentication.md new file mode 100644 index 0000000..6707964 --- /dev/null +++ b/website/s2c_versioned_docs/version-1.0.0/discovery-pairing-authentication.md @@ -0,0 +1,1272 @@ +--- +title: S2 Connect Specification +--- + + +## Introduction + +S2 Connect is a JSON-based protocol specification implementing the EN50491-12-2 "S2" standard for home and building energy management. + +This specification addresses everything needed to create a secure and interoperable implementation of the S2 standard. The communication layer concerns the discovery, the pairing, the application layer communication protocol, the authentication, the message data model and the serialization. It was specifically designed to support multiple deployment scenarios and to give a relatively consistent user experience throughout these different scenarios. + +The protocol is designed to specify communication between two devices, a resource (e.g. a heat pump or EV charger) and a (home) energy management system. It is worth noting that, while this specification focuses on describing the interaction between two components, a (home) energy management is likely to be communicating with multiple resources at the same time. + +## Version + +This version of this specification is based on the following versions of the underlying formal specification files (see [Formal specification and versioning (normative)](#formal-specification-and-versioning-normative) for more details). + +| Project | Files | Version | Reference | +| --- | --- | --- | --- | +| S2 Connect | OpenAPI files | `v1.0` | [Github](https://github.com/flexiblepower/s2-connect) | +| S2 JSON | JSON schemas | `v1.0.0` | [Github](https://github.com/flexiblepower/s2-ws-json) | + +> Note: S2 Connect is not directly linked to the version of S2 JSON. The exact version of S2 JSON that is being used by the CEM and RM is negotiated during session initiation. + +## List of abbreviations + +|Abbreviation | Meaning +|---|---| +| CEM | Customer Energy Manager | +| DNS | Domain Name System | +| DNS-SD | DNS Service Discovery | +| HTTP | HyperText Transfer Protocol | +| HTTPS | HyperText Transfer Protocol Secure | +| LAN | Local Area Network (i.e. a local network, typically constrained to the building) | +| mDNS | Multicast DNS | +| NAT | Network Address Translation | +| REST | Representational state transfer | +| RM | Resource Manager | +| S2 | European standard on Energy Flexibility EN50491-12-2 | +| UUID | Universally Unique IDentifier (see [RFC 9562](https://www.rfc-editor.org/rfc/rfc9562)) | +| WAN | Wide Area Network (i.e. the public internet) | + +## Background (informative) + +### Context +S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other over IP networks. The general architecture is described in the S2 architecture standard introducing CEM and RM functions are defined in EN 50491-12-1:2018, in which also the S2 interface is defined between CEM and RM. + +S2 Connect builds upon two other projects: + +* The **S2 Data model standard** (formally known as EN50491-12-2:2022) describes the data models and interactions for S2 based communication +* The **S2 JSON** project formalizes the data models of the S2 standard into JSON Schemas for a messages-based interaction between CEM and RM + +Both the S2 Standard and S2 JSON can be used independently from S2 Connect. + +### Requirements +S2 Connect aims to provide a standard method for discovering, pairing and letting a Customer Energy Manager (CEM) and a Resource Manager (RM) communicate with each other. The CEM and RM are logical concepts within the S2 architecture, therefore, the S2 standard does not make any assumptions on how and where the CEM and RM are deployed in a real-life situation. In practice, the CEM could be deployed on a local gateway in a LAN or as a server somewhere on the internet (WAN), while the RM could be part of the device itself, deployed on an add-on module or on the internet as well. S2 Connect provides a single solution which can be used between devices connected through a LAN, on the internet, or a combination of those. + +S2 Connect implements the following high-level requirements: + +- Communication must be secure and remain secure, and must be resilient against attackers from the internet or from within the LAN +- Communication between LAN and WAN must work without additional firewall or NAT configuration by the end user +- S2 Connect must be based on widely accepted technology +- Pairing two devices should be simple enough to execute by most end users +- There must be a relatively consistent end user experience regardless of the deployment of the node +- A local RM can run on a computer with constrained hardware +- It must be possible to implement a RM without relying on a user interface for pairing + +The S2 Connect protocol ensures the following four security requirements: + +1. Mutual authentication +2. Integrity of communication +3. Confidentiality of communication +4. Forward secrecy + +Non-repudiation is explicitly not guaranteed by this protocol. + +The entire trust model of S2 Connect is based on the fact that there is already a trust relation between the end user and the CEM and RM. If the CEM and RM do not use adequate security mechanisms, it might be possible to attack the system. + +> "Why doesn't S2 Connect use oAuth?" is a common question. oAuth is mainly designed for accessing protected resources in the cloud and since the S2 CEM and RM would also need to be able to pair on a local network (even without requiring internet access) oAuth 2.0 is simply not a good fit. There is a way to make it work, but since it is such a non-typical way the decision was made to not use oAuth 2.0 for pairing. For more details see here. + +## Terms and definitions (normative) + +This specification uses the concepts that are defined below. + +| Term | Definition | +| --- | --- | +| Access token | A token that is used for setting up an S2 session. It is random binary data and must be generated by a cryptographically secure pseudorandom number generator and have a minimum length of 32 bytes. It is encoded using Base64. Also see [11A. Response status 200](#11a-response-status-200). | +| Communication client | The nodes that behaves as the HTTPS client when initiating an S2 session. | +| Communication server | The nodes that behaves as the HTTPS server when initiating an S2 session. | +| End user | A person or entity that manages nodes. For the purpose of this specification it is assumed that there is already a trust relationship in place between this person and the nodes. This means that the way the trust relationship has been established is out of scope for this specification. | +| End user environment | A restricted area within an application that contains all the nodes that belong to the end user. Also see [Deployment of nodes](#deployment-of-nodes). | +| Endpoint | A service which can handle pairing requests or initiates pairing requests itself. An endpoint can represent one node, but could also represent many. | +| Initiator node | The node that takes the initiative to pair with a Responder node. This is typically the node from which the user initiates the pairing process. It is the counterpart of the Responder node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | +| Node | Refers to an instance of either a CEM or a RM as defined in EN 50491-12-1 and implementing this specification. S2 communication between two nodes can only be established if one of the nodes is a CEM and the other a RM. These nodes must also have the same end user. | +| Node ID | A globally unique identifier for a node in the UUID format. | +| Node ID alias | A short identifier for a node, which is unique in the context of a single endpoint. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Pairing attempt | The process of pairing two nodes. The process can be completed successfully or unsuccessfully. | +| Pairing client | The endpoint that behaves as the HTTPS client when pairing with a node. | +| Pairing code | The pairing code is the string of characters the end user has to copy from the Responder node user interface to the Initiator node user interface, in order to pair the two nodes. The pairing code consists of a pairing token and if required a node ID alias. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Pairing endpoint registry | The central registry that keeps track of publicly available pairing servers. | +| Pairing server | The endpoint that behaves as the HTTPS server when pairing with a node. | +| Pairing token | A secret string of characters, which acts as a proof of the trust relationship between the end user and a node. Also see [The pairing token, the node ID alias and the pairing code](#the-pairing-token-the-node-id-alias-and-the-pairing-code). | +| Pairing URL | The URL of the pairing API on an endpoint (see [Pairing URL](#pairing-url)) | +| Responder node | The node that responds to a request to pair. This is the node that issued the pairing code. It is the counterpart of the Initiator node. Also see [Pairing and unpairing from the perspective of the end user](#pairing-and-unpairing-from-the-perspective-of-the-end-user). | +| Session | A stateful exchange of S2 messages between two S2 nodes | +| User interface | A user interface through which an end user can interact with a node. Interactions between the end user and the user interface must be secure, but this is out of scope for this specification. Examples of a user interface are a web interface, an app or a physical interface (HMI) on a device. | + + +## Architecture (informative) +This section explains the overall architecture and deployment options for CEM and RM instances. + +### Deployment of nodes + +This specification is concerned with connecting an instance of a CEM with an instance of a RM. Either of these instances are referred to as *nodes*, which either have the CEM *role* or the RM *role*. Obviously, it is only possible to pair a node with the CEM role to a node with the RM role. + +Nodes can be deployed locally within the LAN, or somewhere on a server in the WAN. Although their deployment doesn't significantly affect the working of these nodes, there are some key differences between these types of deployment. + +* **WAN** nodes are typically part of a large application that run on many servers and/or on some kind of cloud computing platform. A single application usually serves many users. Each user could have one or multiple nodes. This could for example be a cloud-based energy management system that can connect to many devices. It could also be a cloud environment of a device manufacturer that hosts the RM instances in the cloud. An end user could own multiple devices from this manufacturer, thus the application could host multiple RM instances for this particular user. A group of nodes that a single user can manage within one application is called an *end user environment*. It is also possible that an end user environment contains both CEM and RM instances. The user interface is typically a web interface or a smartphone app. +* **LAN** nodes are typically part of an application that runs on an embedded computer device somewhere in the building. Such a device could be a physical energy management system, an energy flexible device such as a home battery, heat pump or EV charger, or a gateway device which connects to an energy flexible device through some kind of protocol. Often an application will only host a single node, but it is also possible that an application hosts multiple nodes. A device could function completely on its own, but it could also be connected to an internet based application of the manufacturer. The user interface could be a physical human-machine interface on the device, but also be a smartphone app that connects directly to the device (e.g. via Bluetooth), or a smartphone app or web interface that connects to an internet based application of the manufacturer. For energy flexible devices, it is assumed that they could also have no user interface at all, or that they are very constrained when it comes to computing power or memory. It is assumed that a CEM always has a user interface. + +![Deployment_options](./img/deployment_options.png) + +There are three types of S2 connections between nodes possible: + +* **WAN-WAN**: A connection between two nodes deployed in a WAN. Connecting between them is straightforward and can be done based on URLs, based on DNS domain names. It is possible to rely on TLS certificates that can be validated thanks to a public key infrastructure. +* **WAN-LAN**: A connection between a LAN deployed node a WAN deployed node. Since there is almost always a firewall and/or NAT between these two, it is assumed that it is only possible to set up a connection from the LAN to the WAN; not the other way around. Connecting from the LAN node to the WAN node can be done based on a URL, and common TLS certificates can be used thanks to public key infrastructure. +* **LAN-LAN**: A connection between two LAN deployed nodes. It is assumed that in this situation an internet connection might not be available, making it impossible to rely on a public key infrastructure for TLS certificates. That is why for this type of connection self-signed TLS certificates are used. Connections are made based on hostnames that are resolved to IP-addresses using Multicast DNS (mDNS), since IP-addresses are not guaranteed to be stable. Discovering another node could be done using DNS Service Discovery (DNS-SD). + +### Pairing and unpairing from the perspective of the end user +The end user can take the initiative to *pair* a single CEM instance with a single RM instance. This process has to be started with one of the nodes. Which node this is depends on the deployment and implementation decisions of the node, but ideally it could be either one. The node however needs to have a user interface. The node that a user uses to start the pairing process is called the **Initiator node**. The other node receiving the pairing request is called the **Responder node**. + +The first step of pairing is establishing a connection from the Initiator node to the Responder node. This can be done in several ways: +* Enter the Responder node address manually at the Initiator node. +* If the Responder node is deployed in the WAN, the URL could be retrieved through a registry. The end user would have to select the type of node from a list of known node services in its region. +* If both nodes are deployed in the LAN however, nodes can be automatically be discovered. The end user would have to select the node from a list of automatically discovered nodes. + +The second step is entering the pairing code of the Responder node. This is a means for the end user to confirm that these two nodes are allowed to send control signals to each other. The pairing code can be obtained from the Responder node. The pairing code is a (seemingly) random string of characters. This pairing code is typically displayed somewhere in the user interface of the Responder node. It is recommended to use a dynamic token which expires after 5 minutes. However, if the Responder node has a physical presence in the building and doesn't have user interface, there is also the option to have a static pairing code which can be printed on the device. The pairing code serves two purposes: It is both an identifier of the node, as well as a secret that carries the trust relationship from one node, through the end user, to the other node. + +Optionally, the Initiator node can send a signal to the Responder node to indicate that the end user has started the pairing process and has selected the Responder node. This could trigger the user interface of the Responder node to proactively show the pairing code (e.g. through a pop-up) to improve the user experience. + +Once the pairing code is known to the Initiator node, the pairing process is started. It is established that both nodes are compatible and it is verified that the entered pairing code is correct. Pairing could either fail or succeed. + +If pairing is performed successfully, the CEM and RM instances should establish a session with each other and communicate through the exchange of S2 messages. If the connection is interrupted, the instances will automatically try to reestablish the connection. + +Once a CEM is paired, the user has the possibility to command either of the nodes to *unpair*. After unpairing the CEM and RM instances can no longer communicate through S2 (unless the end user pairs them again). + +The pairing process is depicted in the figure below: + +![Pairing_process_user](./img/pairing_process_user.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +participant "Initiator node" as i +participant "Initiator node UI" as iui +actor "End user" as e +participant "Responder node UI" as rui +participant "Responder node" as r + +e->iui: Provide identity of Responder node (e.g. URL) +rui->e: Retrieve pairing code +e->iui: Provide pairing code +i->r: Attempt pairing +r->i: Pairing result (success or failure) +iui->e: Pairing result (success or failure) +@enduml +``` +
+ +### The node and the endpoint + +Within this protocol a clear distinction is made between two types of identities: the one of the **Endpoint** and the one of the **Node** itself. + +A **Node** is an instance of a CEM or a RM. A CEM instance typically belongs to one end user, building or premise, and could be hosted in a dedicated energy management device. A RM instance is typically the representative of one physical energy flexible device, such as a heat pump or a home battery. A node is typically easily recognized by the end user by its brand, device type, model name or maybe even a user given name. + +The **Endpoint** is the application that hosts the nodes. In a LAN deployment an endpoint might only host one node, and these identities may seem very similar. But in a WAN deployment, an endpoint could host thousands of nodes. It could host all kinds of different types of nodes. It could for example be that a certain endpoint hosts RM instances for several brands of devices. Therefore an endpoint needs its own identity, which can be recognized by the end user. + +### Used technology for pairing and communication + +An S2 connection consists of four steps: discovery, pairing, communication and unpairing. For these steps different types of technology are used. + +One of the main technologies the process relies on is HTTPS REST. All interactions based on HTTPS are formally described in OpenAPI specification files. [OpenAPI](https://swagger.io/specification/) is a formal language for specifying HTTP(S) based API's. It can be used to generate reference documentation for developers, as well as stub code for many programming languages. + +#### Discovery + +The first step is finding the Responder node from the Initiator node. In principle this is done based on the pairing URL of the Responder node. However, to improve user experience, two systems exist to find the pairing URL in a more user friendly manner. For more details see [Discovery](#discovery). + +* If the Responder node is deployed in the WAN, the end user can find the endpoint through the pairing endpoint registry. This would result in a list of vendors that offer nodes. +* If both nodes are deployed in the LAN however, the Responder node can be detected automatically through a process based on DNS-SD. This way the user only has to select the desired node to connect to from a list of nodes which were discovered in the LAN. + +#### Pairing + +The pairing process itself is completely based on HTTPS REST. One node behaves as the HTTPS server, and the other as the HTTPS client. This process is described in an OpenAPI file. The process consists of multiple steps. If the pairing process is completed successfully, the nodes will agree on an access token. This token is used to initiate communication or to unpair. + +The endpoint that behaves as the HTTPS server during the pairing process is defined as the *pairing server*, and the client is defined as the *pairing client*. + +Pairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) + +#### Communication + +A communication process is used to set up the actual session that enables the exchange of S2 messages. + +The process always starts with HTTPS based communication, but then is handed over to a protocol which supports a two-way messages based communication channel. Currently the main protocol that is being used is WebSockets, but there are plans to add other options in the future, such as MQTT. This communication setup process using a HTTPS interface is also specified in an OpenAPI file, together with the unpairing process. + +The endpoint that behaves as the HTTPS server during the communication process is called the **Communication server**, and the client is called the **Communication client**. + +It should be noted that pairing and communication are two separate HTTPS interfaces, that don't have to be used in the same way. It could be that a node is an pairing client, but then becomes a communication server. This depends on the deployment of the nodes (see [Pairing details for different deployments](#pairing-details-for-different-deployments)). + +Communication interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) + +After the HTTPS interaction communication with the selected communication protocol is established (WebSocket, other transport protocols such as MQTT will be added in the future). For WebSocket communication, the communication server is always the WebSocket server. This server must use the same TLS certificate as the HTTPS server. + +#### Unpairing + +Either node can take the initiative to unpair from the other node. This is done using the same HTTPS OpenAPI specification and the same HTTPS server and client as the communication. The details for unpairing differ depending if it is the communication server or if it is the communication client that initiates the unpairing process. + +Unpairing interaction is always TLS based (i.e. HTTPS is used). For WAN deployments, normal certificates (signed by a Certificate Authority) are being used. For LAN-LAN deployments self-signed certificates are used. For more information about the use of self-signed certificates, check [Trusting a self-signed root certificate](###Trusting-a-self-signed-root-certificate) + + +### Pairing details for different deployments + +As explained, the pairing process is based on HTTPS REST calls. That means that for every pairing attempt, one node behaves as the HTTPS server, and one node behaves as the HTTPS client. The logical solution would be to make the Initiator node the HTTPS client and the Responder node the HTTPS server. After all, it is the HTTPS client that takes the initiative to contact the HTTPS server. The HTTPS server cannot take the initiative to contact the HTTPS client. + +The objective is to have all nodes be able to be the Initiator node, as well as the Responder node. This is necessary to provide a consistent user experience. The end user might not be aware which node is deployed in the LAN or in the WAN, and then it might be confusing that, for example, his energy management system both provides pairing codes and asks for pairing codes. + +If every node must be able to be the Initiator node in certain situations, and the Responder node in other situations, and the easiest solution is to implement the initiator as HTTPS client and the responder as HTTPS server, you might come to the conclusion that every node needs to be able to behave both as an HTTPS server and as an HTTPS client. + +There are however two situations where this is not possible: + +* **WAN Initiator node and LAN Responder node**: Since the LAN is usually shielded from the WAN through a firewall or NAT, it is assumed that approaching a LAN HTTPS server from a WAN client is not possible. This specifications offers two approaches to this problem: + * Accept this limitation and not allow the WAN node to be the Initiator node. Pairing can only be performed when the LAN node is the Initiator node and the WAN node is the Responder node. Special care must be taken to explain this to the end user. + * Many modern devices or EMS systems are connected to a cloud backend managed by the OEM. If this is the case, it is possible to implement a pairing HTTPS server in the cloud, even though the node itself is in the LAN. If the pairing is performed successfully in the OEM backend, the result of the pairing must be communicated to the node via the existing connection between device/EMS and the OEM backend. This solution is only intended for WAN clients and must not be used by LAN clients. There must always be a method for purely LAN based pairing. +* **LAN initiator RM and LAN responder RM**: Since one of the requirements is that a LAN RM instance can be implemented on restricted hardware, and a TLS enabled HTTPS server is far more memory intensive than an HTTPS client, there is an option to implement a LAN RM instance purely as an HTTPS client. A long-polling mechanism is available to indicate to the HTTPS server that the node is available for pairing. This mechanism is also used to initiate the pairing process from the HTTPS server. In other words: in this specific situation the Initiator node behaves as the HTTPS server, and the Responder node only has to be an HTTPS client. + +![Pairing_direction](./img/pairing_direction.png) + +**Figure:** Overview of all possible ways to pair two nodes + +## Formal specification and versioning (normative) + +This document serves as an overall specification of the S2 Connect protocol. However, where possible, the protocol has been specified in a formal specification language in order to minimize possibilities for different interpretations and allow tooling to assist the implementation of the specification. Since many details are better described in these formal specification files, they are not described in this document. **Where the formal specification files and this document overlap, the formal specification file takes precedence.** + +| Part of specification | Description | Specification format | Location | +| --- | --- | --- | --- | +| S2 Connect pairing API | HTTPS based interaction to pair two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-pairing.yml) | +| S2 Connect connection API | HTTPS based interaction set up a communication channel for S2 messages between two nodes | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-connection-init.yml) | +| S2 Connect WAN pairing endpoint registry API | HTTPS based interface to query the registry | OpenAPI file | [Github](https://github.com/flexiblepower/s2-connect/blob/main/s2-connect-wan-endpoint-registry.yml) | +| S2 JSON message structure | The types of S2 messages that can be exchanges between nodes | JSON schema files | [Github](https://github.com/flexiblepower/s2-ws-json/tree/main/s2-json-schema) | + +### Versioning of OpenAPI files +The pairing API, the session initiation API and the WAN pairing endpoint registry API are formally defined in OpenAPI files. To accommodate future changes to these APIs, the OpenAPI files are versioned. Versioning is done using a `major.minor` scheme. All S2 Connect OpenAPI files share the same version number. + +The minor version is increased when backwards compatible changes are made. Be aware that adding items to certain lists of enums (e.g. the list of supported hash functions) is considered backwards compatible. Other examples of backwards compatible changes are additional properties of JSON files or added operations. + +The major version is increased when non-backwards compatible changes are made. + +The major version of the API is embedded in the base URL of the API as `/v[major]` (e.g. `/v1`). HTTPS server and HTTPS clients can decide to implement several major version of the API in parallel to increase interoperability. In that case server must serve all versions on the same base URL (e.g. `https://hostname.local/pairing/v1/...` and `https://hostname.local/pairing/v2/...`). The server **must** always (even when it only supports one major version of the API) serve an index (e.g. `https://hostname.local/pairing/`) which returns a JSON array with all supported versions as they are defined as part of the URL (e.g. `["v1", "v2"]`). + +### Versioning of JSON Schema files +JSON Schema uses its own versioning scheme, which is based on an `major.minor.patch` scheme. When negotiating the S2 JSON version number, the exact version string **must** be used (e.g. `v1.0.0`). + +### Addressing endpoints +The URL of the pairing and session initiation API are used in the discovery process, pairing process and session initiation process, as wel as the basis for TLS certificates. + +For **WAN** deployed endpoints, the URL **must** be based on a DNS domain name. + +For **LAN** deployed endpoints, the URL **must** be based on an mDNS alias or hostname (e.g. `hostname.local`). It is important that these names are *unique* and *stable*. Unique since there could be multiple instances within the same LAN, and stable because if it changes, the endpoint cannot be found by other endpoints. It should also be noted that the alias used by DNS-SD is presented to the end user. It recommended to choose a name that the end user should recognize and an element for the end user to make a distinction between two devices of the same type, such as a serial number. + +Also see [Pairing URL](#pairing-url). + +### Selecting the version of REST APIs +As explained in the section [Versioning of OpenAPI files](#version) the pairing server, the session initiation server and the WAN pairing endpoint registry can implement multiple versions of the API specification in parallel. As a result, the client **must** always first determine which version of the API it will use, before it can start interacting with the API. + +The image below depicts the interactions between client and server for the process to determine the API version that will be used. + +![image](./img/api_version_selection.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +participant "HTTPS Client" as Client +participant "HTTPS Server" as Server + +Client->Server++: 1. GET to the Pairing URL +Server-->Client: 2. Response status 200 +deactivate Server +Client->Client: 3. Decide pairing version + +@enduml +``` +
+ +#### 1. GET on the Pairing URL +Since the HTTPS client does not know which major versions of the pairing API are implemented by the server, it must first perform a GET request to the [Pairing URL](#pairing-url) (e.g. `https://hostname.local/pairing/`). + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) (with exemption of authenticity) | Pairing is failed, do not proceed with the pairing attempt | + +If the root certificate is self-signed and there is no pinned certificate for this domain name, the fingerprint of TLS certificate must be stored for later check. + +If no checks fail the client **should** proceed to the next step. + +#### 2. Response status 200 +The server responds with a list of implemented major versions of the pairing API. It is formatted as a JSON array containing all the supported version of the pairing API (e.g. `["v1"]`). + +If the HTTPS client does not support any of the provided versions, it means that the two endpoints are not compatible, and that further interaction is not possible. + +#### 3. Decide pairing version +From the provided list of major versions of the pairing API, the HTTPS client **must** select one that is implemented by the HTTPS client itself (typically the highest supported version). The client **should** select the most recent version. + +### HTTPS usage +All HTTP-based interaction in S2 Connect is strictly based on HTTPS (i.e., HTTP over TLS). HTTP (without TLS) **may not** be used for S2 Connect, and it is **recommended** to not run an HTTP (without TLS) server. + +HTTPS clients and servers **must** support HTTP version 1.1. It is **recommended** to support more recent version of HTTP as well. + +## Pairing process (normative) + +The pairing process is based on the trust relation that the end user has with both the CEM and the RM instances. That trust relation is out of scope for this specification and is up to CEM and RM providers to implement. + +For each pairing attempt, one endpoint must be the HTTPS server, while the other is the HTTPS client. The table below defines which endpoint is het HTTPS server or client is which situation. + +| Initiator node | Responder node | Responder is exclusively RM? | Initiator HTTPS role | Responder HTTPS role | WAN pairing server | Remark | +| ----------------- | ----------------- | ---------------------------- | ------------------- | ------------------- | --------------------- | ------ | +| WAN | WAN | Doesn't matter | Client | Server | n/a | | +| WAN | LAN | Doesn't matter | Client | n/a | Server | The WAN pairing server for LAN deployment is optional | +| LAN | WAN | Doesn't matter | Client | Server | n/a | | +| LAN | LAN | Yes | Server | Client | n/a | Through long-polling. Decision to implement the server or the client is up to the RM endpoint. | +| LAN | LAN | Yes | Client | Server | n/a | Alternative for long-polling. Decision to implement the server or the client is up to the RM endpoint. | +| LAN | LAN | No | Client | Server | n/a | | + +> A LAN deployed RM implementation can choose if it implements the HTTPS server, or that it implements the HTTPS client and uses long-polling. This feature exists to accommodate RM implementations with constrained hardware. + +A CEM can be paired with multiple RMs at the same time. A RM can only be paired with one CEM at a time. A node is always available for pairing. When a RM that is already paired with an CEM is paired with another CEM, the initial pairing is automatically unpaired. This automatic unpairing only happens after the new pairing is successfully completed. When a CEM and a RM are being paired when they already are paired with each other, it should be considered as an unpairing and new pairing (which means that a new `accessToken` is being used, and the current communication session should be terminated). + +### Pairing URL +The start of each pairing related interaction is the Initiator node contacting the Responder node via the *pairing URL*. Although discovery provides an option to retrieve this URL in a user friendly manner, entering the pairing URL manually **must** always be an option. Therefore, every Responder node **should** display its pairing URL somewhere (e.g. in its UI), and every Initiator node **should** have an option to pair based on a pairing URL which is manually entered by the end user. + +The pairing URL is the base URL of the pairing API of an endpoint. It **must** include the protocol (`https://`), it **must not** include the version of the API, but it **must** include a trailing slash (e.g. `https://hostname.local/pairing/`). + +For information about the domain name used in the URL see [Addressing endpoints](#addressing-endpoints). + +### Discovery + +In order to ease the pairing process, which is specified below, the discovery process provides a way for nodes to find each other without requiring a user to know the pairing URL of the other node. In other words, the discovery process is a way to provide a node with the pairing URL of another node which is needed to start the pairing process. Alternatively, it should always be possible to initiate the pairing by manually providing the URL by the end user (see [Pairing URL](#pairing-url)). + +There are two mechanisms for discovery: For discovering WAN endpoints there is a central online registry. For discovering endpoints within the same LAN, DNS-SD is used. + +#### WAN pairing endpoint registry + +> Note: At this point the registry is specified, but not yet publicly available + +The purpose of the registry is to facilitate a more user friendly way to determine the pairing URL of the WAN endpoint. Providers of an S2 Connect WAN pairing endpoint can register their endpoint at the registry. The user interface of a CEM or RM could show a list of relevant endpoints to the user (e.g., in a list or drop down menu) with details that would be easily recognizable to the end user (e.g., name and icon). By querying the registry, the user interface can always show an up-to-date list of endpoints. The registry contains filtering functionality to filter endpoints that are relevant in the context. + +The registry uses the same version negotiation mechanism as the other S2 Connect OpenAPI files. Refer to [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis) for information on how clients can select the API version to use. + +The registry contains the following information for each endpoint. For full normative details see the OpenAPI specification files. + +| Property | Description | +| --- | --- | +| `id` | Unique UUID identifier for the record | +| `name` | User facing name of the endpoint | +| `description` | User facing description of the endpoint| +| `icon32` | 32 by 32 pixels icon of the endpoint | +| `icon128` | 128 by 128 pixels icon of the endpoint | +| `icon512` | 512 by 512 pixels icon of the endpoint | +| `pairingUrl` | The pairing URL of the endpoint | +| `regions` | Array of regions in which this endpoint operates, as defined by the ISO 3166-1 alpha-2 country code | +| `status` | Status of the endpoint, can either be `testing` or `public` | +| `cem` | Boolean indicating if the endpoint represents CEM nodes | +| `rm` | Boolean indicating if the endpoint represents RM nodes | + +In order to filter out the relevant endpoint records the API supports the following filters: + +* Region in which the endpoint claims to be active +* Status, which can be `testing` or `public` (when no value provided, the default value will be `public`) +* Whether the endpoint contains CEM nodes +* Whether the endpoint contains RM nodes + +In addition, the number of responses can be limited. An offset can also be provided in order to split the results over multiple requests. + +> Note: Since the pairing code also contains an identifier for the node within the endpoint, the combination of pairing code and endpoint URL is sufficient information to start a pairing attempt. + +#### DNS-SD based discovery +DNS-SD is used to automatically discover nodes from a node that is deployed in the LAN. This method can be used in two ways. + +* To discover another node that is deployed in the LAN, which is the Responder node +* To advertise a [long polling URL](#long-polling) so other Initiator nodes in the LAN could connect to this node + +S2 Connect uses the service type `s2connect` and exclusively uses tcp, since it is an HTTPS based protocol. S2 Connect uses the following DNS-SD values: + +| DNS-SD property | Value for S2 | +| --- | --- | +| Service type | `_s2connect` | +| Protocol | `_tcp` | +| Port | No fixed port, decision is up to the implementation | +| Subtypes | `_cem` and `_rm` | +| Service name | Identical to the hostname (see [here](#addressing-endpoints-normative) for more details) + +An endpoint deployed in the LAN **should** publish it service through DNS-SD once it is ready for pairing, and until it shuts down. + +Two DNS-SD subtypes are used for endpoints. Subtypes can be used to filter services. + + * `_cem` is used when the endpoint contains one or more CEM node + * `_rm` is used when the endpoint contains one or more RM node + * `_cem` and `_rm` are both used when the endpoint contains both CEM and RM nodes + +S2 uses the following key-value pairs in the TXT record when registering for services. In the table below, M indicates a mandatory value and O indicates on optional value. Note that each value has a maximum length of 255 bytes. + +| Record name | M/O | Description +| --- | --- | --- | +| `txtver` | M | Version of this specification of usage of the TXT record. **Must** be the literal string value `1` for this version | +| `e_name` | O | The name of this endpoint (identical to the `name` property in the `EndpointDescription` object as defined in de OpenAPI specification) | +| `e_logoUrl` | O | The logoUrl of this endpoint (identical to the `logoUrl` property in the `EndpointDescription` object as defined in de OpenAPI specification) | +| `pairingUrl` | O | The base URL of the pairing API of this endpoint (see [Pairing URL](#pairing-url)). If no value is provided, a `longpollingUrl` **must** be provided. +| `longpollingUrl` | O | The base URL of the pairing API of this endpoint on which the longpolling feature is implemented. The URL should be provided excluding the version name but including the last slash (e.g. `https://hostname.local/pairing/`). Only needs to be provided when longpolling is supported. Can only be provided if the value for `deployment` is equal to `LAN`. + +> Note: It is mandatory to provide a value for at least one of the properties `pairingUrl` and `longpollingUrl`. Providing both is also possible. + +The receiver of the service description **must** use the URL provided in the TXT record; not the hostname or IP-address and port associated with the service registry. + +> Note: You may have noticed that the full URL of the endpoint is used in the TXT record, even though the endpoint and port are already exposed by DNS-SD itself. This is done to avoid any problems with TLS certificates, which are bound to a certain mDNS domain name. + +> Scanning for endpoints could for example be done using the following [avahi](https://avahi.org/) command: +> +> `avahi-browse -r _s2connect._tcp` +> +> Registering an endpoint could for example be done using the following avahi command: +> +> `avahi-publish-service -s "EVSE1038" _s2connect._tcp 443 "txtvers=1" "e_name=brand" "pairingUrl=https://EVSE1038.local:443/pairing/" --sub _rm._sub._s2connect._tcp` + +### The pairing token, the node ID alias and the pairing code + +The pairing token is a random string of characters that is generated by the Responder node. It is a secret which is transferred by the end user to the Initiator node, and then is verified during the pairing process. Since there will be many cases where the end user has to manually type in the pairing token, the pairing token has to be short enough to make it easy for the end user to type in, but long enough to make it secure. + +The pairing token may consist out of lower case letters, upper case letters and numbers. The pairing token **must** be generated by a cryptographically secure pseudorandom number generator. The pairing token is typically dynamically generated when the user requests the pairing token at the user interface of the Responder node. Dynamically generated pairing tokens **must** expire after a duration; five minutes is the recommended duration. However, energy flexible devices that do not have a user interface are allowed to have a static pairing token, that for example can be printed somewhere on the physical device. Static pairing tokens do not expire. Dynamic pairing tokens **must** contain at least 4 characters. Static paring tokens **must** contain at least 6 characters. Pairing tokens may be as long as the developer deems necessary. + +| Type of pairing token | Minimal length | Validity | Regular expression | +| --- | --- | --- | --- | +| Dynamic pairing token | 4 characters | Limited duration, 5 minutes is recommended | `^[0-9a-zA-Z]{4,}$` | +| Static pairing token | 6 characters | Indefinitely | `^[0-9a-zA-Z]{6,}$` | + +An endpoint can host multiple nodes. When attempting to pair a certain node, the endpoint needs to know exactly which of its nodes this pairing attempt is aimed at. Nodes are uniquely identified with their node ID. Since this node ID is a UUID, it is pretty long and cumbersome to type in. That is why an endpoint can assign its nodes a *node ID alias*. This is an identifier that is intended to be short, and only unique within the context of this particular endpoint. Node ID aliases could be assigned by the endpoint whenever new nodes are created, but also could be generated dynamically only when someone is attempting to pair to this node. This way, node ID aliases have a short live, and can be reused by other nodes at other moments. This allows to use shorter node ID aliases. Node ID aliases are a string of characters, which may include lower case letters, upper case letters and numbers. Node ID aliases are ideally as short as possible (at least one character), but should of course be long enough to allow the endpoint to uniquely identify a node. When an endpoint only contains one node, there is no need for a node ID alias. + +The **node ID alias** can be validated with the following regular expression: + +``` +^[0-9a-zA-Z]+$ +``` + +Although the pairing token and the node ID alias are two separate strings, which are treated completely differently in the pairing process, they are presented together to the user as one string: the *pairing code*. The pairing code is simply the node ID alias, followed by a dash ('-'), followed by the pairing token. When there is no node ID alias, the pairing code is simply identical to the pairing token. + +> Note: The node ID alias is not secret and is exchanged during a pairing attempt. The pairing token is secret. It is never exchanged; it is only used as input for the challenge response function. + +``` +When no node ID alias is used (i.e. the endpoint only contains one node): + [pairing code] = [pairing token] +When a node ID alias ID is used: + [pairing code] = [node ID alias]-[pairing token] +``` + +Alternatively, the **pairing code** can be validated with the following regular expression: + +``` +^([0-9a-zA-Z]+-)?[0-9a-zA-Z]{4,}$ +``` + +The pairing code allows us to transfer two pieces of information by only bothering the end user once. Due to its format the Initiator node can easily extract the node ID alias and the pairing token from the pairing code by splitting the string at the dash. + + +### Challenge response process + +This protocol uses a two-way challenge response process to verify that both nodes have the same pairing token. For this process it doesn't matter which node has issued the pairing token and which node has the pairing token that was entered by the end user. The reason a two-way challenge response process is used to verify the pairing token is that it allows to establish trust without having to expose the pairing token. Both a challenge and a response are binary data, which are encoded using Base64. + +The challenge that is generated by the HTTPS Client is called the `clientHmacChallenge`. The response to this challenge, generated by the HTTPS server, is called the `clientHmacChallengeResponse`. The challenge that is generated by the HTTPS server is called the `serverHmacChallenge`. The response to this challenge, generated by the HTTPS client, is called the `serverHmacChallengeResponse`. + +A challenge is a nonce; a random binary data. It **must** be generated by a cryptographically secure pseudorandom number generator and it **must** have a minimal length of 32 bytes. The response is calculated based on the function described below. Both the generator of the challenge and the receiver of the challenge calculate the response based several input parameters. Since both nodes should have the same input, both nodes should calculate the same response. The node that received the challenge sends it back to the node that generated the challenge. Now the node that generated the challenge simply has to check if the received response is identical to the expected response that he calculated himself. + +The algorithm to calculate the response is based on the HMAC (hash-based message authentication code) function. This function has a *key* and a *message* as arguments. Most programming languages have a function or library available that provides HMAC functions. + +The HMAC function itself uses a cryptographic hash function for its calculations. Since cryptographic hash functions might contain vulnerabilities, this protocol uses a simple selection mechanism for the cryptographic hash function. The HTTPS client sends with the requestPairing HTTPS request a list of supported hash functions. In the response the HTTPS server indicates which hash function it has selected from this list. This function **must** be used for all response calculations during het pairing attempt. Currently there is only one hash function available (SHA256), but other options might be added in the future. + +In order to avoid man-in-the-middle attacks, information about the connection is also used as input for calculating the response. When both nodes are deployed in the LAN, the SHA256 fingerprint of the server certificate is used. In other scenarios, the domain name of the server is included in the calculation of the response. + +Note that the challenge and response are binary data. Both are encoded using Base64 and must also be decoded before they can be used. SHA256 certificate fingerprints are encoded into a hexadecimal string, and must be decoded as hexadecimal string before it can be used as input (note that fingerprint strings usually contain colons to separate bytes). The pairing token and domain name are strings, which need to be converted into binary data using the ASCII table. + +The exact function to calculate the response depends on the deployment of the nodes. + +``` +When the pairing server is deployed in the LAN: + R = HMAC(C, T || F) + +When the pairing server is deployed in the WAN: + R = HMAC(C, T || D) +``` + +Where: +| Symbol | Type | Meaning | +| ------ | ------- | ---- | +| `R` | Binary data | Response | +| `HMAC` | Function | HMAC function for the selected cryptographic hash function. The first argument is the secret key, the second argument is the message. | +| `C` | Binary data | Challenge | +| `T` | Binary data | Pairing token | +| `F` | Binary data | SHA256 fingerprint of the TLS server certificate (i.e. leaf certificate) | +| `D` | Binary data | The domain name of the HTTPS server, including subdomains, without protocol or trailing slashes (e.g. `pairing.s2.example.com`) | +| `\|\|` | Function | Concatenation function | + +### LAN-LAN only interactions + +The are several REST operations that are only to be implemented by LAN endpoints, and that can only be used by other LAN endpoints. They are described in this section. + +WAN endpoints **cannot** implement these operations. It is **recommended** that WAN endpoints respond with status code 404 when they receive requests for these operations. + +Since these operations are only intended for endpoints within the same LAN, the pairing server must check if the requests originates from within the same LAN. Therefore the pairing server **must** check if the request originated from the same subnet. This functionality **must** be implemented in such a way that it works with both IPv4 and IPv6. When a request does not originate from the same subnet the server **must** respond with status code 401. + +> Note: There are some network configurations imaginable where it would be desirable to pair two LAN nodes that are not in the same subnet. In that case automatic discovery via DNS-SD will not work, but pairing by manually entering the pairing URL and pairing code can still be used in those cases. + +#### Getting endpoint information + +> This section is only applicable for LAN-LAN pairing + +Once a LAN endpoint has discovered a LAN pairing endpoint (through DNS-SD), it still knows very little about the endpoint. There are two REST operations that allow an HTTPS client to query information of a server endpoint: performing a GET on `/endpoint` and performing a GET on `/nodes`. + +These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the Initiator node is the HTTPS client and the Responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). + +Before the HTTPS client can start interaction with the server, it must first select a version of the API to use. See [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis). For full normative details see the OpenAPI specification files. + +* The client can perform an HTTPS GET request on the path `/endpoint` to receive the remote endpoint details. +* The client can perform an HTTPS GET request on the path `/nodes` to receive a list of node details for all the nodes represented by the endpoint. + +Note that there is no authentication for these operations. It is assumed that these operations are only exposed within the LAN. + +#### Pre-pairing interaction + +> This section is only applicable for LAN-LAN pairing + +Once the end user has selected a Responder node it wants the Initiator node to pair with, the end user probably still has to retrieve the pairing code from the Responder node. In order to improve the user experience, the Initiator node can send a *prepare pairing* signal to the Responder node. The Responder node **may** use this signal to proactively show the pairing code in its user interface, for example in the form of a pop-up or notification. This saves the end user the trouble of searching where to find the pairing token in the user interface. It is also possible for the Initiator node to send a *cancel prepare pairing* signal to the Responder node, in case the end user has no longer selected the Responder node it wants to pair with. Sending these signals **must** be implemented by the client, but only when there is a clear distinction between the moment the prepare pairing signal is sent and when the actual pairing starts. The receiver of these signals **may** process these signals by showing the pairing token in its user interface. When the prepare pairing signal is sent, it is not guaranteed that a cancel prepare pairing or a pairing attempt will follow. + +These operations **must** be implemented by LAN deployed endpoints, but **must not** be implemented by WAN deployed endpoints. These operations can be used in the situation where the Initiator node is the HTTPS client and the Responder node is het HTTPS server (for the situation where it is the other way around see [Long-polling](#long-polling)). + +Note that there is no authentication for these operations. It is assumed that these operations are only exposed within the LAN. + +Before sending signals the HTTPS client **must** have selected the version of the pairing API that will be used (see [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis)). + +##### Sending the prepare pairing signal + +The client can send the prepare pairing signal to the server by sending an HTTPS POST request to the path `/preparePairing`. The client must perform the following checks before sending information: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) | Sending signal failed, do not proceed with sending signal | + +The client **must** send the following information in the request. For full normative details see the OpenAPI specification files. + +| Property | Description | +| --- | --- | +| `clientNodeDescription` | Details of the node at the client that the end user intents to pair with the node at the server | +| `clientEndpointDescription` | Details of the client endpoint | +| `serverNodeId` | The node ID of the node at the server that the end user intents to pair with the node at the client (see [Getting endpoint information](#getting-endpoint-information) for details on how to retrieve the server node ID) | + +The server **must** perform the checks in the table below. For the checks with HTTP status 400, a `PairingResponseErrorMessage` must be send. In that case, the contents of the `additionalInfo` field is supposed the be helpful and up to the implementer. + +| Check | Status code | Type of `PairingResponseErrorMessage` when check fails | +| --- | --- | --- | +| Is the request properly formatted and does it follow the schema? | 400 | `ParsingError` | +| Does it recognize the `serverNodeId`? | 400 | `NodeNotFound` | +| Are the endpoint and node ready for pairing? | 400 | `Other` | +| Does the targeted node have a different role than the Initiator node (i.e. you cannot pair two RM's or two CEM's)? | 400 | `InvalidCombinationOfRoles` | +| Does the request originate from inside the subnet? | 401 | n/a | + +If no checks fail the server **should** respond with HTTP status code 204. + +##### Cancelling the prepare pairing signal + +If the client sent a prepare pairing signal the the server, and the end user has indicated in some way that it is no longer indented to pair with the node, it **should** send a cancel prepare pairing signal. It can do that by sending an HTTPS POST request to the path `/cancelPreparePairing`. The client must perform the following checks before sending information: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) | Sending signal failed, do not proceed with sending signal | + +The client **must** send the following information in the request. For full normative details see the OpenAPI specification files. + +| Property | Description | +| --- | --- | +| `clientNodeId` | The node ID of the node at the client that the end user intents to pair with the node at the server | +| `serverNodeId` | The node ID of the node at the server that the end user intents to pair with the node at the client | + +The server **should** respond with HTTP status code 204 (even when it does not recognize the `clientNodeId` or `serverNodeId`). However, if the request originated from outside the subnet the server **must** respond with status 401. + +#### Long-polling for constrained endpoints in the LAN + +> This section is only applicable for LAN-LAN pairing + +The long-polling feature is intended to support endpoints that only want to implement an HTTPS client, and not an HTTPS server. Typically this is because the endpoint runs on constrained hardware. An endpoint is only allowed to only implement the client if it exclusively hosts nodes that have the RM role. + +Imagine having two endpoints, one only hosting a CEM node, and one only hosting a RM node. The RM runs on constrained hardware, and only implements the HTTPS client. Therefore there are are two situations: + +1. **The RM is the Initiator node and the CEM is the Responder node**: The CEM issues a pairing code, the end users enters the pairing code in the UI of the RM. The RM endpoint (HTTPS client) then sends a normal HTTPS request to the CEM endpoint (HTTPS server) to initiate pairing. The normal pairing process can be used, and long-polling is not required. +2. **The CEM is the Initiator node and the RM is the Responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTPS server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTPS client), because the RM endpoint has no HTTPS server implementation. + +For the second situation the long-polling feature can be used. It can be used by the Initiator node (the HTTPS server) to notify the Responder node (the HTTPS client) it wants to pair. + +> Informative: Long-polling is a technique that allows the server to send signals to the client without a significant delay, and without relying on additional technologies such as Websockets or Server-Sent Events. The common alternative is polling, where the client sends a request on a regular interval; let's say every 30 seconds. Polling creates a delay from the perspective of the server. If the server wants to send something to the client, it has to wait until the client contacts the server; which in the worst case 30 seconds. With long-polling the server doesn't immediately respond the the request (a hanging HTTPS request). It responds immediately when the server wants the client to do something, or just before the request would time out. After receiving the response from the server the client immediately opens a new request to allow the server to send signals the client again. + +The long-polling feature fulfills the following functionality: +* Make the existence of the client known to server, together with the nodes IDs of the nodes that are represented by the client endpoint +* Send the `NodeDescription` and `EndpointDescription` of nodes represented by the client when requested by the server +* Send a prepare pairing signal or cancel prepare pairing from the server to the client for a particular node ID +* Send the signal from the server to the client to initiate pairing for a particular node ID +* Send an error message from the client to the server when pairing cannot be performed + +Before the HTTPS client can start interaction with the server, it must first select a version of the API to use. See [Selecting the version of REST APIs](#selecting-the-version-of-rest-apis). For full normative details see the OpenAPI specification files. + +A client capable of long-polling **should** initiates long-polling when it encounters a endpoint through DNS-SD that indicates that is available for long-polling requests. When the endpoint represents zero nodes the client **cannot** attempt long-polling. When the endpoint stops advertising itself, or only its long-polling indication disappears from DNS-SD, the client **should** stop the long-polling process for that server. The client **must** also stop when it is no longer capable of pairing. + +The server **must** always respond within 25 seconds after receiving the request. The client **must** use a request time-out of at least 30 seconds. + +The client starts the process by doing a POST request to the `/waitForPairing` path. For full normative details see the OpenAPI specification files. The request body contains a list of objects. The client **must** always provide an object for each node ID it represents. The items in the list have a mandatory property `clientNodeId` and optional parameters `clientNodeDescription`, `clientEndpointDescription`. The client should only provide values for these properties when requested by the server. The object also contains the optional property `errorMessage`, which only should be used when an error has occurred before pairing. + +The server **must** check if the requests originates from within the same subnet. If it does not, it **must** reply with HTTP status code 401. + +When the server wants the client to immediately do a new request, it responds with status code 204. When it wants the client to do something, it responds with status 200 and a response body containing a list. This list contains an object only for node IDs represented by the client, that the server wants to do something with. This object contains the mandatory properties `clientNodeId` and `action`. The `action` property is an enumeration indicating an action the server wants to execute for a specific node. The possible action values are `sendNodeDescription`, `preparePairing`, `cancelPreparePairing` and `requestPairing`. + +The table below indicates how the client should respond to the requests of the server. Note that the server could send multiple actions (for different node IDs) in the same response. The server **cannot** provide multiple objects for the same node ID in one response. + +| Status code | Value `action` | Pairing code entered? | What should the client do | What should the client include in the next request body | +| --- | --- | --- | --- | --- | +| 204 | n/a | n/a | Only send a next request | Only the `clientNodeId` | +| 200 | `sendNodeDescription` | n/a | Only send the next request | The `clientNodeId`, `clientNodeDescription` and `clientEndpointDescription` | +| 200 | `preparePairing` | n/a | Prepare pairing for the mentioned node ID and send the next request | Only the `clientNodeId` | n/a | +| 200 | `cancelPreparePairing` | n/a | Cancel prepare pairing for the mentioned node ID and send the next request | Only the `clientNodeId` | +| 200 | `requestPairing` | Yes | Initiate the pairing for the mentioned node ID and send the next request | Only the `clientNodeId` | +| 200 | `requestPairing` | No | Initiate the pairing for the mentioned node ID and send the next request | Only the `clientNodeId`, and for the associated object provide an `errorMessage` with value `NoValidTokenOnPairingClient` | +| 400 | n/a | n/a | Stop long-polling until next time long-polling is advertised through DNS-SD | n/a | +| 401 | n/a | n/a | Stop long-polling, do not attempt long-polling with this node again | n/a | +| 500 | n/a | n/a | Wait before trying to send the next request | Only the `clientNodeId`| + +2. **The CEM is the Initiator node and the RM is the Responder node**: The RM issues a pairing code (a dynamic pairing code through its UI, or a static pairing code for example through a sticker on the hardware), the end user enters the pairing code in the UI of the CEM. Now the CEM endpoint (HTTPS server) cannot use the normal pairing process, since it has no way to contact the RM endpoint (HTTPS client). + +When the server sends the `requestPairing` action, the node on the client must already have issued a pairing token. If the node uses a dynamic pairing code, it could be the case that the pairing code has expired, or that no pairing code has been issued in the first place. In that case the client **must** perform a new request with an `errorMessage` containing the value `NoValidTokenOnPairingClient` in the object associated with the node ID of the node should have attempted to pair. + +The activity diagram below summarizes the complete long-polling process from the perspective of the client. + +![image](./img/long-polling_activity_diagram.png) + +### Pairing interaction + +The pairing process itself consists of several HTTPS interactions between client and server. The image below depicts a successful pairing process between two nodes. + +![image](./img/pairing_http_process.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +participant "HTTPS Client" as Client +participant "HTTPS Server" as Server + +'compatibility check +Client->Server: 1. POST /[version]/requestPairing +activate Server +Server->Server: 2. Calculate clientHmacChallengeResponse +Server-->Client: 3. Response status 200 +deactivate Server + +Client->Client: 4. HTTPS Client checks clientHmacChallengeResponse + +Note over Client: HTTPS Client now trusts HTTPS Server + +Client->Client: 5. Calculate serverHmacChallengeResponse + +alt Pairing server is Communication Server + Client->Server: 6A. POST /[version]/requestConnectionDetails + activate Server + Server->Server: 7A. HTTPS Server checks serverHmacChallengeResponse + Note over Server: HTTPS Server now trusts HTTPS Client + Server-->Client: 8A. Response status 200 + deactivate Server +else Pairing server is Communication Client + Client->Server: 6B. POST /[version]/postConnectionDetails + activate Server + Server->Server: 7B. HTTPS Server checks serverHmacChallengeResponse + Note over Server: HTTPS Server now trusts HTTPS Client + Server-->Client: 8B. Response status 204 + deactivate Server +end + +Client->Server++: 9. POST /[version]/finalizePairing +Server-->Client: 10. Response status 204 +deactivate Server + +Note over Client, Server: Pairing finalized + +@enduml +``` +
+ +#### 0. Precondition + +Before two node can be paired, the following preconditions must be met. + +1. The HTTPS server and the HTTPS client can only start with a pairing request when they are fully initialized and have all the details of the nodes it represents available. +2. The HTTPS client must have the pairing URL of the endpoint (see [Pairing URL](#pairing-url)) +3. The HTTPS client must have selected the version on the pairing API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) +4. Both nodes must have a pairing token available. Either because they issued this token themselves, or because the end user has provided it through the user interface. + +> Note: The Initiator node could be the HTTPS server or the HTTPS client + +If the HTTPS client does not fulfill these preconditions, it **cannot** send the first HTTPS request of the pairing process. + +#### 1. POST /[version]/requestPairing +In the first POST request the client provides the server with same information about itself. The main purpose of this is to check if these two nodes are compatible. + +The client sends the following information (for full details see the OpenAPI specification file): + +| Information | Description | +| --- | --- | +| `clientNodeDescription` | Information about the node that wants to pair, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the Initiator node | +| `clientEndpointDescription` | Information about the client endpoint. An important field is the deployment (LAN or WAN). | +| `nodeId` | The nodeID of the node that is being targeted (this filed can be omitted if the client only knows the `nodeIdAlias` or when the endpoint only represents one node). | +| `nodeIdAlias` | The nodeIdAlias of the node that is being targeted (this field can be omitted if the client only knows the `nodeId` or when the endpoint only represents one node) | +| `supportedCommunicationProtocols` | List of supported communications protocols of the client | +| `supportedS2MessageVersions` | List of supported S2 message versions by the client | +| `supportedHmacHashingAlgorithms` | List of supported hashing algorithms for the challenge response function (currently only `SHA256` is supported and **must** be present) | +| `clientHmacChallenge` | The challenge of the client for the challenge response process (see [Challenge response process](#challenge-response-process) | +| `forcePairing` | Indicate if the nodes must pair, even though they (currently) do not support the same S2 message versions (this could in the future be solved with a software update) | + +Be aware that the client may never provide a value for `nodeId` and `nodeIdAlias` at the same time. When the server endpoint only represents one node, both properties may be omitted. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | +| Check if same fingerprint is used as previous request (when applicable) | Pairing is failed, do not proceed with the pairing attempt | + +If no checks fail the client **should** proceed to the next step. + + +The server **must** perform the checks in the table below to make sure that it can proceed with this request. If one of these checks fail, the server should respond with an HTTP status 400 and a `PairingResponseErrorMessage`. The contents of the `additionalInfo` field is supposed the be helpful and up to the implementer. + +| Check | Type of `PairingResponseErrorMessage` when check fails | Can be ignored when `forcePairing` is true ?| +| --- | --- | --- | +| Is the request properly formatted and does it follow the schema? | `ParsingError` | No | +| Does it recognize the `nodeIdAlias`? | `NodeNotFound` | No | +| Are the endpoint and node ready for pairing? | `Other` | No | +| If no `nodeIdAlias` provided, does this endpoint indeed only represent one node? | `NoNodeIdProvided` | No | +| Does the targeted node have a different role than the Initiator node (i.e. you cannot pair two RM's or two CEM's)? | `InvalidCombinationOfRoles` | No | +| Does the server accept any of the provided hashing algorithms for the challenge response process? | `IncompatibleHmacHashingAlgorithms` | No | +| Is there overlap between the communication protocols? | `IncompatibleCommunicationProtocols` | Yes | +| Is there overlap between the S2 message versions? | `IncompatibleS2MessageVersions` | Yes | +| If the targeted node on the HTTPS server is the Initiator node, did the end user provide a valid pairing token? | `NoValidPairingTokenOnPairingServer` | No | +| If the targeted node on the HTTPS server is the Responder node, does the node have a pairing token which has not expired? | `NoValidPairingTokenOnPairingServer` | No | +| Is this is a WAN pairing server for a LAN endpoint, does the client have a WAN deployment? | `Other` | No | + +> Note: If the node that is being paired is an RM which is already paired, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation must be unpaired. + +> Note: If the targeted node is already paired with the Initiator node, the pairing process proceeds. When the paring process is finished successfully the existing pairing relation is maintained. + +> Note: This is the only step where it is checked if the pairing code has expired. If the pairing token expires after this step, but during the pairing process, the pairing process will continue. A pairing attempt is limited to 15 seconds. + +If no checks fail the server **should** proceed to the next step. + +#### 2. Calculate clientHmacChallengeResponse +The server selects an hashing algorithm for the challenge response function from the list that was provided by the client. This has to be a hashing algorithm that the server considers secure. The server calculates a response to the provided `clientHmacChallenge`. For details see [Challenge response process](#challenge-response-process). + +To mitigate brute-force attacks, the server **must** enforce a mandatory delay of one second before sending its response to the client (step 3). For any given node at the server, pairing attempts **must** be handled sequentially, such that each second only one pairing attempt can be processed for a node. Pairing attempts targeting different nodes **may** be processed in parallel. This way, a server representing multiple nodes is not globally limited to one pairing attempt per second, but instead enforces the one-second rate limit independently per node. + +#### 3. Response status 200 +In order to formulate a response, the server **must** generate a `pairingAttemptId`. This is an identifier that **must** be generated by a cryptographically secure pseudorandom number generator and encoded using Base64. This identifier is used to keep track of all the HTTPS interactions during the pairing attempt, and **must** be provided by the HTTPS client as a header with all subsequent interactions. A pairing attempt **must** be completed within 15 seconds, or else the server **must** assume the pairing attempt has failed. + +The server responds with the following information (for full details see the OpenAPI specification file): + +| Information | Description | +| --- | --- | +| `pairingAttemptId` | The generated identifier for this pairing attempt | +| `serverNodeDescription` | Information about the node that is being targeted, such as brand, logo and type. Important fields include `id` (the node ID) and `role` of the Responder node | +| `serverEndpointDescription` | Information about the server endpoint. An important field is the deployment. | +| `selectedHmacHashingAlgorithm` | The hashing algorithm for the challenge response function as selected in step 2 | +| `clientHmacChallengeResponse` | The response to the challenge provided by the HTTPS client as calculated in step 2 | +| `serverHmacChallenge` | The challenge created by the HTTPS server for the challenge response process (see [Challenge response process](#challenge-response-process).) | + +The client **must** perform the following checks of this data. + +| Check | How to proceed if check fails | +| --- | --- | +| Can the contents of the response be parsed? | Do not proceed with the pairing attempt | +| Is the response formatted according to the schema? | call `/finalizePairing` where `success` is `false` if `pairingAttemptId` is available | +| Is the role of the node at the server compatible? | call `/finalizePairing` where `success` is `false` | + +If no checks fail the server **should** proceed to the next step. + +#### 4. HTTPS Client checks clientHmacChallengeResponse +The HTTPS client checks the `clientHmacChallengeResponse` provided by the HTTPS server in step 3. It does that by calculating the response itself, and checking if the results is identical to the `clientHmacChallengeResponse`. + +If the result is identical, the client **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt. It **must** attempt to inform the HTTPS server of this by doing an HTTPS request to `finalizePairing` where the value of `success` must be `false`. + +Note that in case of a local server, the TLS certificate fingerprint is part of the challenge. So if the challenge succeeds, the certificate fingerprint is correct, and the certificate can be trusted. The client **must** pin the self-signed CA (root) certificate, and trust this certificate for the remainder of the pairing relation. + +#### 5. Calculate serverHmacChallengeResponse +The HTTPS client calculates a response to the provided `serverHmacChallenge` using the hashing algorithm as indicated in the `selectedHmacHashingAlgorithm`. For details see [Challenge response process](#challenge-response-process). + +From hereon the process branches into two scenarios, depending on if the HTTPS client will be the communication client or the communication server. See [Mapping the CEM and RM to communication server or client](#mapping-the-cem-and-rm-to-communication-server-or-client) for which node will perform which role for communication. + +If the HTTPS server will be the communication *server* steps 6A, 7A and 8A **should** follow. If the HTTPS server will be the communications *client* steps 6B, 7B en 8B **should** follow. + +#### 6A. POST /[version]/requestConnectionDetails +> Note: The `pairingAttemptId` must be provided through a header for this HTTPS request + +The HTTPS client makes a request for the connection details. This request also serves as a way to send the HTTPS server the `serverHmacChallengeResponse` calculated in step 5. + +If the `pairingAttemptId` is not recognized by the server (or has expired), the server **must** respond with status code 401. + +If the request was not understood by the server for any other reason, the server **must** respond with status 400. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | + +If no checks fail the client **should** proceed to the next step. + + +#### 7A. HTTPS server checks serverHmacChallengeResponse +The HTTPS server checks the `serverHmacChallengeResponse` provided by the HTTPS client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. + +If the result is identical, the server **should** proceed to the next step. If the result is not identical, the server **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTPS client anymore. If the HTTPS client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). + +#### 8A. Response status 200 +The server **must** generates an access token for the HTTPS client. The access token is random binary data and **must** be generated by a cryptographically secure pseudorandom number generator and **must** have a minimum length of 32 bytes. It is encoded using Base64. The access token **cannot** be used by the Initiator node until the pairing process is completed. + +The server responds with two pieces of information: + +| Information | Description | +| --- | --- | +| `initiateSessionUrl` | The base URL for the connection process (does not include the version number) | +| `accessToken` | The access token that was generated for this node | + +If the response is understood and properly formatted, the HTTPS client **should** proceed to the next step. Otherwise the HTTPS client **must** stop the pairing attempt. It **must** attempt to inform the HTTPS server of this by doing an HTTPS request to `finalizePairing` where the value of `success` must be `false`. + +#### 6B. POST /[version]/postConnectionDetails +> Note: The `pairingAttemptId` must be provided through a header for this HTTPS request + +The HTTPS client sends the connection details to the HTTPS server. This request also serves as a way to send the HTTPS server the `serverHmacChallengeResponse` calculated in step 5. + +In this case the pairing server will become the communication client. Once the pairing server becomes the communication client, it does not know what the certificate that the communication server will use. That is why it needs to provide the fingerprint of its CA (root) certificate using the property `certificateFingerprint`. This property is a map, where the key of the map is the hashing algorithm used to generate the fingerprint, and the value is the fingerprint itself. The hashing function `SHA256` and the related fingerprint **must** always be provided. The communication client **must** pin this certificate to the domain name of the communication server. + +| Information | Description | +| --- | --- | +| `serverHmacChallengeResponse` | The response for the challenge response process | +| `initiateSessionUrl` | The base URI for the connection process (does not include the version number) | +| `accessToken` | The access token that was generated for this node | +| `certificateFingerprint` | A map with the fingerprint of the CA (root) certificate. The key of the map is the name of the hashing algorithm used to generate the fingerprint, the value is the fingerprint itself. The key `SHA256` must always be provided. | + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | + +If no checks fail the client **should** proceed to the next step. + +The server **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| `pairingAttemptId` is recognized | Respond with status code 401 | +| Request could not be parsed correctly | Respond with status code 400 | + +If no checks fail the server **should** proceed to the next step. + +#### 7B. HTTPS server checks serverHmacChallengeResponse +The HTTPS server checks the `serverHmacChallengeResponse` provided by the HTTPS client in step 6A. It does that by calculating the response itself, and checking if the results is identical to the `serverHmacChallengeResponse`. + +If the result is identical, the server **should** proceed to the next step. If the result is not identical, the client **must** stop the pairing attempt by responding with HTTP status code 403. The `pairingAttemptId` cannot be used by the HTTPS client anymore. If the HTTPS client wants to make another attempt, it **must** start again at step 1 (starting with the API version selection process is also allowed). + +#### 8B. Response status 204 +The server confirms it has accepted the response and received the connection details by responding with HTTP status 204. + +#### 9. POST /[version]/finalizePairing +> Note: The `pairingAttemptId` must be provided through a header for this HTTPS request + +If all interaction has been successful until this point, the HTTPS client **must** do a request to finalize the pairing attempt. The provided value for `success` **must** be `true`. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) | Pairing is failed, do not proceed with the pairing attempt | + +If no checks fail the client **should** proceed to the next step. + +The server **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| The `pairingAttemptId` is correctly recognized | respond with status code 401 | +| The request is not understood for any other reason | respond with status code 400 | + +If no checks fail the server **should** proceed to the next step. + +Receiving a `/finalizePairing` request marks the completion of the pairing attempt for the HTTPS server. If the HTTPS server issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTPS client. + +#### 10. Response status 204 +To confirm the successful completion of the pairing attempt, the HTTPS server responds to the client with HTTP status code 204. This response marks the completion of the pairing attempt for the HTTPS client. If the HTTPS client issued an access token during this pairing attempt, it can now be used by a communication client to set up an S2 connection. The `pairingAttemptId` can no longer be used by the HTTPS client. + +If the HTTPS server was using a self-signed TLS certificate, the HTTPS client can now store the self-signed root certificate. The client **must** check that this is the CA certificate that is used for all future interaction with this endpoint. The HTTPS server is allowed to use a new self-signed server certificate, as long as it is signed by the self-signed CA certificate that was used during the pairing process. + +#### Interruption of the process +A pairing attempt has a maximum duration of 15 seconds. That means that once a `pairingAttemptId` has been issued, this `pairingAttemptId` cannot be used after 15 seconds since it was issued. From the perspective of the HTTPS server, any pairing attempt that is not completed in 15 seconds (with success or not) is considered a failed attempt. From the perspective of the HTTPS client, if the server does not respond within 15 seconds since it received the `pairingAttemptId`, it must consider the pairing attempt as failed. If the HTTPS client wants to make another attempt, it should start again at step 1 (starting with the API version selection process is also allowed). + +#### Invalid interactions +If the server receives a wrong HTTPS request (e.g. `/postConnectionDetails` while it was expecting `/requestConnectionDetails`) or when it receives the requests in the wrong order (e.g. `/finalizePairing` with `success` = `true` before calling `/requestConnectionDetails`) it **must** respond with a status 400 and consider the pairing attempt as failed. The only exception is receiving the same request twice. + +## S2 Connection (normative) + +After two nodes have been paired, the nodes exchange S2 messages over a secure connection. + +The following mechanism **must** be used to initiate a secure connection between two nodes. Client authentication is based on a one-time use communication token that needs to be renewed every time a new S2 session is created. The communication client will always attempt to set up an S2 connection with the communication server when there is no connection. For more details see [Reconnection strategy](#reconnection-strategy). + +### Mapping the CEM and RM to communication server or client + +The CEM and RM roles defined by the S2 protocol are distinct from the server and client roles of the pairing process. The following rules apply to determine whether the RM or CEM acts as a client or server for the communication initialization. + +* If a connection is set up between a WAN node and a LAN node, the WAN node must act as a communication server, and the local node must act as a communication client. +* If a connection is set up between two nodes that are similarly deployed (i.e. both in WAN, or both in LAN), the CEM must act as a communication server, and the RM must act as a communication client. + +There are four scenarios for CEM and RM deployment, and applying the rules above yields the following: + +| CEM deployment | RM deployment | CEM acts as | RM acts as | +|----------------|---------------|-------------|------------| +| WAN | WAN | Communication server | Communication client | +| WAN | LAN | Communication server | Communication client | +| LAN | WAN | Communication client | Communication server | +| LAN | LAN | communication server | Communication client | + +> Note: A device developed solely for use as an RM in a LAN setup will never function as a communication server. + +### Session initiation + +During the pairing process an `accessToken` is generated by the node which will be the communication server and sent to the node that will be the communication client. This `accessToken` can be used by the communication client to set up a session with the communication server for exchanging S2 messages. The `accessToken` does not expire, but it can only be used (successfully) once to set up a session. Each time a new session is made, the `accessToken` will be renewed. The communication server will generate a new `accessToken` and sends it to the communication client. Since this `accessToken` is the only means to connect two nodes once they are paired, the session initiation process makes sure that both nodes confirm that they have successfully persisted the new `accessToken` before invalidating the old `accessToken`. + +![session initiation](./img/session-initiation.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +participant "HTTPS Client" as Client +participant "HTTPS Server" as Server + +Client->Server++: 1. POST /[version]/initiateSession +Server->Server: 2. Generate new pending accessToken +Server-->Client--: 3. Response status 200 +Client->Client: 4. Store pending accessToken +Client->Server++: 5. POST /[version]/confirmAccessToken +Server->Server: 6. Activate new accessToken for this node ID +Server-->Client--: 7. Response status 200 +Client -> Client : 8. Remove old accessToken +@enduml +``` + +
+ +#### 0. Precondition + +Before a node can initiate a session, it needs four things. + +1. The HTTPS server and the HTTPS client can only start with a communication request when they are fully initialized and have all the details of the nodes it represents available. +2. The HTTPS client must have the base URL of the session initiation API (e.g. `https://hostname.local/connection/`) +3. The HTTPS client must have selected the version on the session initiation API that will be used (see [Selecting the version of the pairing or session initiation API](#selecting-the-version-of-rest-apis)) +4. The two nodes must have been paired successfully and must have an accessToken for this pairing + +If the HTTPS client does not fulfill these preconditions, it **cannot** send the first HTTPS request of the session initiation process. + +#### 1. POST /[version]/initiateSession +Since there are situations in which the client cannot know for sure which `accessToken` the communication server uses for this pairing, the communication client must keep a persisted list of `accessTokens` (which will typically contain only one `accessToken`). + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) | Initiation is failed, do not proceed with the initiation attempt | + +If no checks fail the client **should** proceed to the next step. + +The client sends the following information (for full details see the OpenAPI specification file). In addition, the `accessToken` is sent through a header. + +| Information | Description | +| --- | --- | +| `clientNodeId` | The node ID of the communications client that wants to connect to the server. | +| `serverNodeId` | The node ID of the communications server that the client wants to connect to. | +| `clientNodeDescription` | Information about the node, such as brand, logo and type. This only needs to be provided if the communication client wants to update this information, otherwise the communication server will assumer the stored information is still valid. | +| `clientEndpointDescription` | Information about the client endpoint. This only needs to be provided if the communication client wants to update this information, otherwise the communication server will assumer the stored information is still valid. | +| `supportedCommunicationProtocols` | List of supported communications protocols of the client | +| `supportedS2MessageVersions` | List of supported S2 message versions by the client | + +The server **must** perform the checks in the table below to make sure that it can proceed with this request. If one of these checks fail, the server should respond with an HTTP status 400 and a `CommunicationDetailsErrorMessage` or with HTTP status 401. The contents of the `additionalInfo` field of the `CommunicationDetailsErrorMessage` is supposed the be helpful and up to the implementer. + +| Check | Response | What should the client do with this message? | +| --- | --- | --- | +| Is the request properly formatted and does it follow the schema? | `CommunicationDetailsErrorMessage` with errorMessage `ParsingError` | Retry later | +| Was this node ID paired with this node, but was it unpaired? | `CommunicationDetailsErrorMessage` with errorMessage `NoLongerPaired` | Do not retry, inform end user | +| Is this `clientNodeId` paired with the `serverNodeId`? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | +| Is the `serverNodeId` known? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | +| Is this the correct `accessToken` for this node ID? | Status code 401 | Try with other `accessToken` if possible. Otherwise do not retry, inform end user | +| Is there overlap between the communication protocols? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleCommunicationProtocols` | Retry later | +| Is there overlap between the S2 message versions? | `CommunicationDetailsErrorMessage` with errorMessage `IncompatibleS2MessageVersions` | Retry later | +| Are the endpoint and node ready for connecting? | `CommunicationDetailsErrorMessage` with errorMessage `Other` | Retry later | + +#### 2. Generate new pending `accessToken` + +For each paired node the server saves an active `accessToken`. In addition to that, the server also has a list for pending `accessToken`s, that were generated but not yet confirmed by the client. This list contains entries, each consisting of an `accessToken`, the node IDs of the client and server nodes and a timestamp. + +The server generates a new `accessToken` and saves this together with the node ID and the current time as in entry in the list of pending tokens. The `accessToken` **must** be generated by a cryptographically secure pseudorandom number generator. + +#### 3. Response status 200 + +In the request the client supplied a list of supported communication protocols and S2 messages versions. The server must select one of the options that were provided by the client. + +The server sends the following information (for full details see the OpenAPI specification file). + +| Information | Description | +| --- | --- | +| `selectedCommunicationProtocol` | The communication protocol that was selected by the server | +| `selectedS2MessageVersion` | The S2 message version that was selected by the server | +| `accessToken` | The newly generated pending `accessToken` | +| `serverNodeDescription` | Information about the node at the server, such as brand, logo and type. This only needs to be provided if the communication server wants to update this information, otherwise the communication client will assumer the stored information is still valid. | +| `serverEndpointDescription` | Information about the server endpoint. This only needs to be provided if the communication server wants to update this information, otherwise the communication client will assumer the stored information is still valid. | + +The client **must** perform the checks in the table below to make sure that it can proceed with this request. + +| Check | What should the client do? | +| --- | --- | +| Is the request properly formatted and does it follow the schema? | Do not proceed and try again later with step 1 | +| Was the selected S2 message version offered in the request? | Do not proceed and try again later with step 1 | +| Was the selected communication protocol offered in the request? | Do not proceed and try again later with step 1 | + +#### 4. Store pending accessToken +The client adds the pending `accessToken` to its list of `accessTokens`, but does not yet remove the old one. If the client is not able to persist the pending `accessToken` (e.g. because the storage device or the DBMS is not available), the client does not proceed with the process. Once the client is able to persist `accessTokens` again, it can retry to set up a session starting with step 1. + +#### 5. POST /[version]/confirmAccessToken +The client confirms to the server that it has successfully persisted the pending `accessToken`. The **pending** `accessToken` is provided through the header of the request. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) | Do not proceed with session, try again later | + +If no checks fail the client **should** proceed. + +#### 6. Activate new `accessToken` for this node ID + +If the provided `accessToken` is in the list pending `accessToken`s, and the token was generated not more than **15 seconds** ago, the server now makes the pending `accessToken` the active `accessToken` for this pairing of nodes (thereby invalidating the old `accessToken`). Also, the entry is removed from the list of pending `accessToken`s. + +If the provided `accessToken` is not in the list of pending `accessTokens`s, the server must not accept the session and respond with status 401. The client can try again later starting at step 1. + +If the server is not able to active the new `accessToken` (e.g. because the storage device or the DBMS is not available), the server must not accept the session and responds with an error code 500. The client can try again later starting at step 1. + +#### 7. Response status 200 + +The communication server sends the details and credentials to open a socket for communicating the S2 messages. The exact contents of this message depend on the selected communication protocol. In any case it will be a JSON object containing the field `communicationProtocol`. The presence on other fields will depend on the value of the `communicationProtocol` field. + +If the response is not understood by the communication client, the client **should** retry later. + +#### 8. Remove old accessToken + +Step 7 functions as a confirmation to the communication client that the communication server has activated the new `accessToken` for this pairing. The old `accessToken` cannot be used anymore, so the communication client must remove the old `accessToken` from the list of `accessToken`s. + +#### Interruption of the process +Once the communication server has generated a new pending `accessToken`, it must be confirmed within 15 seconds by the communication client. If this doesn't happen, a client will have to start the process from step 1. + +If the communication client doesn't receive a response to confirming the new `accessToken` (step 7), it does not know if the server has activated the new `accessToken`, or if the old `accessToken` is still in place. It now has (at least) two `accessToken`s in its list, and does not know for certain which one is activate at the communication server. It should try all the accessTokens sequentially. If it finds an `accessToken` that is accepted by the communication server, it can remove the other `accessTokens`. + +### Reconnection strategy +After session initiation, the actual S2 communication starts via the selected transport protocol. At some point this session will terminate. Unless the reason for termination is that the nodes have been unpaired, the communication client **must** try to reconnect with the communication server. In this case the client **must** always start with the session initiation process (it is not allowed to reconnect using the transport protocol that was selected last time). + +An exponential back-off strategy for reconnecting **must** be used, increasing the time between reconnection attempts at every failed attempt. It is recommended to use the following strategy for calculating the delay for the nth attempt to reconnect. The delay time starts when a failed connecting attempt is finished. + +`delay_n = random(0, min(max_delay, base_delay × 2^n))` + +Where: + +| Variable | Description or recommended value | +| --- | --- | +| `n` | The number of the reconnection attempt (starting at 0) | +| `base_delay` | 2 seconds | +| `max_delay` | 600 seconds | + +### WebSocket based communication + +This section specifies how to use WebSocket Secure as the S2-over-TCP/IP application layer protocol. + +The WebSocket client **must** run on the communication client and the WebSocket server on the communication server. + +The choice for a WebSocket as application layer communication protocol has the advantage that the session concept is intrinsically introduced with the communication protocol. All S2 communication happens in the context of a (stateful) S2 session which is catered for by the WebSocket session. So, the S2 session matches the WebSocket session. + +The client **must** perform the following checks during this request: + +| Check | How to proceed if check fails | +| --- | --- | +| [Check TLS certificate](#certificate-validation) | Websocket connection failed, do not proceed with the connection attempt | + +If no checks fail the client **should** proceed to the next step. + +#### Opening the connection +Opening the WebSocket connection is performed by sending a GET request to the URL as provided during the connecting initiation process together with an upgrade request, as specified by [RFC6455](https://datatracker.ietf.org/doc/html/rfc6455). When using HTTP/2, also refer to [RFC8441](https://datatracker.ietf.org/doc/html/rfc8441). + +#### Authentication +For each S2 WebSocket session the client **must** authenticate itself using the commToken in the authorization header of the websocket connection request, following [RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://datatracker.ietf.org/doc/html/rfc6750). + +#### Encrypted connection (WSS) + +Communication over the WebSocket endpoint **must** be encrypted following [RFC 6455 The WebSocket Protocol](https://datatracker.ietf.org/doc/html/rfc6455). S2 **MUST NOT** be sent over unencrypted channels. Therefore a wss connection (the URL starting with wss://) must be used. + +#### Compression + +The WebSocket Protocol ([RFC6455](https://datatracker.ietf.org/doc/html/rfc6455)) has an extension for compression: [**RFC 7692**](https://datatracker.ietf.org/doc/html/rfc7692.html) implementing so called per-message-deflate compression. https://datatracker.ietf.org/doc/html/rfc7692 + +The RFC 7692 compression extension is widely supported by WebSocket libraries and since JSON is exchanged as plain text messages, it is expected to save a large amount of data. Therefore, implementations of S2 WebSockets **SHOULD** support RFC 7692 and **SHOULD** enable it whenever possible. + +#### Keepalive & heartbeat (ping / pong) +WebSockets by default have a **keepalive** and a **heartbeat mechanism**. Keepalive is designed to keep the connection open while heartbeat is designed to check the latency and check the connection is still working. This means that periodically a ping frame is sent to the server (endpoint) and in response a pong frame is sent. + +S2 WebSockets implementations **should** send a ping frame every 30 seconds, and **must not** wait more than 60 seconds between sending ping frames. Ping and pong frames **may** include a payload. + +For more details see [RFC6455 Section 5.5.2](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2%5C%5C). + +#### Termination + +An S2 session can be terminated in different ways: + +* In case a node unexpectedly becomes unavailable, the WebSocket connection **CAN** timeout. This will cause an S2 session to be terminated. More details about the timeout can be found [in the heartbeat section](#keepalive--heartbeat-ping--pong) +* a node **CAN** terminate the S2 session by sending the S2 terminate message, including an optional earliest time that the session can be restored. The other node can take this into account in planning and (in the case of a client) deciding when to attempt to reconnect. +* After two nodes have unpaired, the S2 WebSocket connection **MUST** be terminated immediately. + +### Communication - JSON messages + +S2 Connect uses [S2 JSON](https://github.com/flexiblepower/s2-ws-json) for its JSON schema specification. + +When using S2 Connect, the `Handshake` and `HandshakeResponse` messages (as defined by S2 JSON) **can not** be sent. They are redundant by the pairing and session initiation process. + +#### State of communication + +![State of Communication](./img/state-of-communication.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml + +hide empty description +[*] --> WebSocketConnected + +WebSocketConnected : ResourceManagerDetails, PowerMeasurement, \n PowerForecast can be exchanged + +WebSocketConnected --> ControlTypeActivated: Activate ControlType +ControlTypeActivated --> WebSocketConnected: Deactivate ControlType + +ControlTypeActivated: In addition to messages in\nthe "Initialized" state,\nControlType specific message\ncan now be exchanged as well + +WebSocketConnected --> WebSocketDisconnected: Termination of Session +ControlTypeActivated --> WebSocketDisconnected: Termination of Session + +WebSocketDisconnected --> [*] +@enduml +``` +
+ +> Note: When no Control Type is selected (i.e. `NO_CONTROL_TYPE`), the protocol is in the "WebSocket Connected" state. + +| State | Messages that can be sent by CEM /received by RM | Messages that can be sent by RM / received by CEM | +| --- | --- | --- | +| WebSocket Connected | SelectControlType
SessionRequest
ReceptionStatus | ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType PEBC activated | PEBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | PEBC.EnergyConstraint
PEBC.PowerConstraint
RevokeObject InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType PPBC activated | PPBC.EndInterruptionInstruction
PPBC.ScheduleInstruction
PPBC.StartInterruptionInstruction
SelectControlType
SessionRequest
ReceptionStatus | PPBC.PowerProfileDefinition
PPBC.PowerProfileStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType OMBC activated | OMBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | OMBC.Status
OMBC.SystemDescription
OMBC.TimerStatus RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType FRBC activated | FRBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | FRBC.ActuatorStatus
FRBC.FillLevelTargetProfile
FRBC.LeakageBehaviour
FRBC.StorageStatus
FRBC.SystemDescription
FRBC.UsageForecast
FRBC.TimerStatus
RevokeObject
InstructionStatusUpdate ResourceManagerDetails
PowerMeasurement
PowerForecast
SessionRequest
ReceptionStatus | +| ControlType DDBC activated | DDBC.Instruction
SelectControlType
SessionRequest
ReceptionStatus | DDBC.ActuatorStatus
DDBC.AverageDemandRateForecast
DDBC.SystemDescription
DDBC.TimerStatus
RevokeObject
InstructionStatusUpdate
ResourceManagerDetails
PowerMeasurement PowerForecast
SessionRequest
ReceptionStatus | + +## Unpairing process (normative) + +Unpairing can be initiated by either node, and **should** only be done when instructed by the end user. The node that did not take the initiative to unpair **should** try to inform the end user that the node is no longer paired. + +### Unpairing by the communication client + +If the communication client takes the initiative to unpair, it first **should** close the communication session regarding this pairing (if there is one). Then it **must** call the `/[version]/unpair` HTTPS API endpoint of the server using its `accessToken` (after discovering the API version at the server, the same way as steps 1 to 3 as mentioned in [Session initiation](#session-initiation)). Upon receiving the unpair call from a client, the server node **must** remove all security information related to this pairing. Then the client **must** remove all security information of the communication server related to this pairing. + +### Unpairing by the communication server + +If the server takes the initiative to unpair, first it **must** remove all security information related to client node. After that, if there is an active S2 session, it **should** send an S2 [SessionRequest](/model-reference/Common/SessionRequest/) message with type [RECONNECT](/model-reference/Common/SessionRequestType/) to the client. The next `/[version]/initiateConnection` HTTPS API request **must** fail with the `No longer paired` response to let the client know that it is not paired anymore. + +Client and server **can** keep other (non-security) information for, for example, user experience purposes. + +## Security (normative) + +### Brute-force protection +To prevent brute-force pairing request, the server **must** implement rate limiting on the requestPairing endpoint. For more details see [2. Calculate clientHmacChallengeResponse](#2-calculate-clienthmacchallengeresponse). + +### DDoS countermeasures +Especially WAN endpoints are vulnerable for DDoS attacks. It is recommended for WAN endpoints to take countermeasures against these attacks. The nature of these countermeasures are outside of the scope of this specification. + +### TLS Certificates +All HTTP and WebSocket communication uses TCP over TLS with server certificates. The server certificates **must** be exchanged and validated during the initiation of the connection (HTTPS and WSS). This is default usage of most networking libraries. + +There are two possible types of certificates for TLS communication. The first option is using a public server certificate, that is created through a Public Key Infrastructure (PKI) and thus signed by a public CA. The other option (only applicable to LAN servers) is to use a self-signed certificate. The latter is needed because a LAN server is not able to obtain a certificate that has been issued by a CA for its local mDNS domain name. This is also the only situation where self-signed certificates are allowed. + +The following image shows the difference. On the left a public root CA that is publicly known and trusted, on the right, a self-signed root certificate, that is unknown and its trustworthiness has to be achieved in another way. + +![image.png](./img/certificate-chains.png) + +
+Image generated using the following PlantUML code: + +``` +@startuml +struct PublicRootCA +struct PublicIntermediateCA +struct PublicServerCertificate + +PublicRootCA --> PublicIntermediateCA +PublicIntermediateCA --> PublicServerCertificate + + +struct SelfSignedCA +struct LocalServerCertificate + +SelfSignedCA --> SelfSignedCA +SelfSignedCA --> LocalServerCertificate +@enduml +``` +
+ + +#### Trusting a self-signed root certificate +LAN deployed nodes will have a self-signed root certificate, and a server (leaf) certificate which is signed by the self-signed root certificate. + +The `endpoint`, `nodes`, `preparePairing` and `cancelPreparePairing` operations can be called before the pairing has happened. For these operations the client **must** accept the self-signed certificates, even though it cannot trust the root certificate. + +Also when attempting pairing (the `requestPairing` operation) the client **must** accept the self-signed certificate. During the pairing process trust is established through a two-sided challenge response mechanism. If the two-sided challenge response succeeds, that means that the client can now trust the server of the node it is paired with. The client **must** pin the CA (root) certificate to the domain name. This means that it must check for every furter interaction (for the duration of the pairing relation with this server) if it still uses the same CA (root) certificate. Alternatively, when the pairing server becomes the communication client, the pairing client will send the fingerprint of the self-signed CA (root) certificate that the communication server will use (see [6B. POST /[version]/postConnectionDetails](#6b-post-versionpostconnectiondetails)). + +When performing session initiation and unpairing the communication client **must** validate that server certificate and check that the certificate was signed by the self-signed root certificate that was pinned in the previous step. + +#### Updating the certificates +A server can update its leaf certificate. When a cloud server updates its certificate, it **MUST** be signed by a CA, so a client can check its validity. A server **SHOULD** update its server certificate at least once every 6 months. + +If the server is deployed in the LAN, and thus uses a self-signed root certificate, the root certificate **SHOULD** be created with a validity period which is long enough for the expected lifetime of the server (or the device that hosts the server). If the used crypto for the the CA (root) certificate is broken, or the lifetime of the server is longer than the validity of the certificate, the server **MUST** create a new self-signed CA certificate and all clients need to be paired again. Like cloud servers, a local server **SHOULD** update its leaf certificate at least once every 6 months. + +#### Certificate validation +Certificate checks are mentioned several times in this specification. The check consists of these parts: +- Authenticity: In case of a WAN server, is the certificate issued by a trusted CA (using the chain of trust)? For a LAN server, is the root certificate pinned to the server’s domain name? +- Domain name validation: has the certificate been issued for the (local) domain name of the server? +- Expiration date: has the certificate not been expired? +- Integrity: Has the certificate not been tampered with? This is verified by checking whether the signature is valid. +- Cryptography check: has an allowed crypto algorithm been used? See the section on [Cipher suites](#cipher-suites). + +With the exemption of some cases where the authenticity cannot be verified yet, all checks must be performed every time a TLS connection is set up. + +### Cipher suites + +Security levels of cipher suites will change over time. To stay secure, the used cipher suites should be updates regularly and adhere to regular updates. All nodes **MUST** follow **ONLY** the accepted crypto libraries as defined in Accepted crypto algorithms. This list will be kept up-to-date. When changes are made to the list of accepted crypto libraries, all nodes **MUST** follow these changes within half a year. diff --git a/website/s2c_versioned_docs/version-1.0.0/img/api_version_selection.png b/website/s2c_versioned_docs/version-1.0.0/img/api_version_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..de15c49126a36a9065e76fe79d47e87fc2835efc GIT binary patch literal 5345 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%0A8}U-ab;{ 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 EKWdZcdH?_b literal 0 HcmV?d00001 diff --git a/website/s2c_versioned_docs/version-1.0.0/img/long-polling_activity_diagram.drawio b/website/s2c_versioned_docs/version-1.0.0/img/long-polling_activity_diagram.drawio new file mode 100644 index 0000000..8d1f50d --- /dev/null +++ b/website/s2c_versioned_docs/version-1.0.0/img/long-polling_activity_diagram.drawio @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/s2c_versioned_docs/version-1.0.0/img/long-polling_activity_diagram.png b/website/s2c_versioned_docs/version-1.0.0/img/long-polling_activity_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..4d376e879e2cc58e9a92ad93be2f81e4461077cc GIT binary patch literal 49717 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

aUFU#cUy8z-?!EH>Xv*`` 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 literal 0 HcmV?d00001 diff --git a/website/s2c_versioned_docs/version-1.0.0/img/pairing_direction.png b/website/s2c_versioned_docs/version-1.0.0/img/pairing_direction.png new file mode 100644 index 0000000000000000000000000000000000000000..072137b8009e2815a72dd8503fddb08742212837 GIT binary patch literal 77621 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 T5~oLWW$)gWyOnuE@9F;n>IIsP literal 0 HcmV?d00001 diff --git a/website/s2c_versioned_docs/version-1.0.0/img/pairing_http_process.png b/website/s2c_versioned_docs/version-1.0.0/img/pairing_http_process.png new file mode 100644 index 0000000000000000000000000000000000000000..33a17e9198e9a02f97b40992e36c4aafda89a4d3 GIT binary patch literal 29541 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�q@YG$DYs)%h4JRPO>s@ymrvZqJ^Py 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 literal 0 HcmV?d00001 diff --git a/website/s2c_versioned_docs/version-1.0.0/img/powerpoint_img_source.pptx b/website/s2c_versioned_docs/version-1.0.0/img/powerpoint_img_source.pptx new file mode 100644 index 0000000000000000000000000000000000000000..4610aadee46de157235b7e5ac0058fca43010df5 GIT binary patch literal 204588 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