You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -257,7 +260,7 @@ The callback function is called with the following three arguments:
257
260
*`handshake` {Object} - A handshake object
258
261
*`protocols` {Set} - A set of subprotocols purportedly supported by the client.
259
262
*`identity` {String} - The identity portion of the connection URL, decoded.
260
-
*`password` {String} - If HTTP Basic auth was used in the connection, and the username correctly matches the identity, this field will contain the password (otherwise `undefined`). Read [HTTP Basic Auth](#http-basic-auth) for more details of how this works.
263
+
*`password` {String} - If HTTP Basic auth was used in the connection, and the username correctly matches the identity, this field will contain the password (otherwise `undefined`). Read [Security Profile 1](#security-profile-1) for more details of how this works.
261
264
*`endpoint` {String} - The endpoint path portion of the connection URL. This is the part of the path before the identity.
262
265
*`query` {URLSearchParams} - The query string parsed as [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams).
263
266
*`remoteAddress` {String} - The remote IP address of the socket.
@@ -333,7 +336,7 @@ Returns a `Promise` which resolves when the server has completed closing.
333
336
-`endpoint` {String} - The RPC server's endpoint (a websocket URL). **Required**.
334
337
-`identity` {String} - The RPC client's identity. Will be automatically encoded. **Required**.
335
338
-`protocols` {Array<String>} - Array of subprotocols supported by this client. Defaults to `[]`.
336
-
-`password` {String} - Optional password to use in [HTTP Basic auth](#http-basic-auth). (The username will always be the identity).
339
+
-`password` {String} - Optional password to use in [HTTP Basic auth](#security-profile-1). (The username will always be the identity).
337
340
-`headers` {Object} - Additional HTTP headers to send along with the websocket upgrade request. Defaults to `{}`.
338
341
-`query` {Object|String} - An optional query string or object to append as the query string of the connection URL. Defaults to `''`.
339
342
-`callTimeoutMs` {Number} - Milliseconds to wait before unanswered outbound calls are rejected automatically. Defaults to `60000`.
Once created, the `Validator` is immutable and can be reused as many times as is required.
706
709
707
-
## HTTP Basic Auth
710
+
## OCPP Security
711
+
712
+
It is possible to achieve all levels of OCPP security using this module. Keep in mind though that many aspects of OCPP security (such as key management, certificate generation, etc...) are beyond the scope of this module and it will be up to you to implement them yourself.
713
+
714
+
### Security Profile 1
715
+
716
+
This security profile requires HTTP Basic Authentication. Clients are able to provide a HTTP basic auth password via the `password` option of the [`RPCClient` constructor](#new-rpcclientoptions). Servers are able to validate the password within the callback passed to [`auth()`](#serverauthcallback).
708
717
709
-
###Usage Example
718
+
#### Client & Server Example
710
719
711
720
```js
712
721
constcli=newRPCClient({
@@ -727,17 +736,19 @@ await server.listen(80);
727
736
awaitcli.connect();
728
737
```
729
738
730
-
###Identities containing colons
739
+
#### A note on identities containing colons
731
740
732
741
This module supports HTTP Basic auth slightly differently than how it is specified in [RFC7617](https://datatracker.ietf.org/doc/html/rfc7617). In that spec, it is made clear that usernames cannot contain colons (:) as a colon is used to delineate where a username ends and a password begins.
733
742
734
743
In the context of OCPP, the basic-auth username must always be equal to the client's identity. However, since OCPP does not forbid colons in identities, this can possibly lead to a conflict and unexpected behaviours.
735
744
736
-
In practice, it's not uncommon to see violations of RFC7617 in the wild. All major browsers allow basic-auth usernames to contain colons, despite the fact that this won't make any sense to the server. RFC7617 acknowledges this fact. The prevalent solution to this problem seems to be to simply ignore it.
745
+
In practice, it's not uncommon to see violations of RFC7617 in the wild. All major browsers allow basic-auth usernames to contain colons, despite the fact that this won't make any sense to the server; RFC7617 acknowledges this fact in its text. The established solution to this problem seems to be to simply ignore it.
737
746
738
747
However, in OCPP, since we have the luxury of knowing that the username must always be equal to the client's identity, it is no longer necessary to rely upon a colon to delineate the username from the password. This module makes use of this guarantee to enable identities and passwords to contain as many or as few colons as you wish.
This security profile requires that the central system offers a TLS-secured endpoint in addition to HTTP Basic Authentication [(as per profile 1)](#security-profile-1).
795
+
796
+
When implementing TLS, keep in mind that OCPP specifies a minimum TLS version and minimum set of cipher suites for maximal compatibility and security. Node.js natively supports this minimum set of requirements, but there's a couple of things you should keep in mind:
797
+
798
+
* The minimum TLS version should be explicitly enforced to prevent a client from using a weak TLS version. The OCPP spec currently sets the minimum TLS version at v1.2 (with v1.1 and v1.0 being permitted for OCPP1.6 only under exceptional circumstances).
799
+
* The central server role must support both RSA & ECDSA algorithms, so will need a corresponding certificate for each.
800
+
801
+
#### TLS Client Example
802
+
803
+
```js
804
+
const { RPCClient } =require('ocpp-rpc');
805
+
806
+
constcli=newRPCClient({
807
+
endpoint:'wss://localhost',
808
+
identity:'EXAMPLE',
809
+
password:`monkey1`,
810
+
wsOpts: { minVersion:'TLSv1.2' }
811
+
});
812
+
813
+
awaitcli.connect();
814
+
```
815
+
816
+
#### TLS Server Example
817
+
818
+
Implementing TLS on the server can be achieved in a couple of different ways. The most direct way is to [create an HTTPS server](https://nodejs.org/api/https.html#httpscreateserveroptions-requestlistener), giving you full end-to-end control over the TLS connectivity.
console.log(`${handshake.identity} connected using TLS:`, {
850
+
password:handshake.password, // the HTTP auth password
851
+
cert:tlsClient.getCertificate(), // the certificate used by the server
852
+
cipher:tlsClient.getCipher(), // the cipher suite
853
+
version:tlsClient.getProtocol(), // the TLS version
854
+
});
855
+
accept();
856
+
});
857
+
```
858
+
859
+
Alternatively, your TLS endpoint might be terminated at a different service (e.g. an Ingress controller in a Kubernetes environment or a third-party SaaS reverse-proxy such as Cloudflare). In this case, you may either try to manage your server's TLS through configuration of the aforementioned service, or perhaps by inspecting trusted HTTP headers appended to the request by a proxy.
860
+
861
+
### Security Profile 3
862
+
863
+
This security profile requires a TLS-secured central system and client-side certificates; This is also known as "Mutual TLS" (or "mTLS" for short).
864
+
865
+
The client-side example is fairly straight-forward:
866
+
867
+
#### TLS Client Example
868
+
869
+
```js
870
+
const { RPCClient } =require('ocpp-rpc');
871
+
const { readFile } =require('fs/promises');
872
+
873
+
// Read PEM-encoded certificate & key
874
+
constcert=awaitreadFile('./client.crt', 'utf8');
875
+
constkey=awaitreadFile('./client.key', 'utf8');
876
+
877
+
constcli=newRPCClient({
878
+
endpoint:'wss://localhost',
879
+
identity:'EXAMPLE',
880
+
wsOpts: { cert, key, minVersion:'TLSv1.2' }
881
+
});
882
+
883
+
awaitcli.connect();
884
+
```
885
+
886
+
#### TLS Server Example
887
+
888
+
This example is very similar to the example for [security profile 2](#security-profile-2), except for these changes:
889
+
890
+
* The HTTPS server needs the option `requestCert: true` to allow the client to send its certificate.
891
+
* The client's certificate can be inspected during the auth() callback via `handshake.request.client.getPeerCertificate()`.
892
+
* A HTTP auth password is no longer required.
893
+
894
+
**Note:** If the client does not present a certificate (or the presented certificate is invalid), [`getPeerCertificate()`](https://nodejs.org/api/tls.html#tlssocketgetpeercertificatedetailed) will return an empty object instead.
0 commit comments