Skip to content

Commit e47edff

Browse files
authored
change AuthKey encoding to hex (#4)
* change AuthorizationKey to Hex format * migrate to Mongoose base64 encoder * revise AuthKey hex encoding * fix compilation under MG v6.14 * prepare for release
1 parent e336c4b commit e47edff

File tree

7 files changed

+191
-293
lines changed

7 files changed

+191
-293
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# Changelog
22

3-
## [Unreleased]
3+
## [v1.1.0] - 2024-05-21
44

55
### Changed
66

77
- Adopt `bool isConnected()` from `Connection` interface ([#7](https://github.com/matth-x/MicroOcppMongoose/pull/7))
88
- Do not copy cert into heap memory ([#10](https://github.com/matth-x/MicroOcppMongoose/pull/10))
9+
- Migrate to base64-encoder in Mongoose ([#4](https://github.com/matth-x/MicroOcppMongoose/pull/4))
910

1011
### Added
1112

@@ -18,6 +19,10 @@
1819
- FTP moved into a new project [MicroFtp](https://github.com/matth-x/MicroFtp) ([#5](https://github.com/matth-x/MicroOcppMongoose/pull/5))
1920
- Custom config `Cst_CaCert` ([#10](https://github.com/matth-x/MicroOcppMongoose/pull/10))
2021

22+
### Fixed
23+
24+
- Encode AuthorizationKey in Hex ([#4](https://github.com/matth-x/MicroOcppMongoose/pull/4))
25+
2126
## [1.0.0] - 2023-10-20
2227

2328
_First release._

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# MicroOcppMongoose
22
Mongoose WebSocket adapter for MicroOcpp
33

4-
**Formerly ArduinoOcppMongoose**: *see the statement on the former ArduinoOcpp [project site](https://github.com/matth-x/MicroOcpp)*
5-
64
## Dependencies
75

86
The following projects must be available on the include path:
@@ -19,8 +17,6 @@ The setup is done if the following include statements work:
1917
#include <MicroOcpp.h>
2018
```
2119

22-
The last dependency is [base64-converter by Densaugeo](https://github.com/Densaugeo/base64_arduino), but it is already included in this repository. Thanks to [Densaugeo](https://github.com/Densaugeo) for providing it!
23-
2420
## License
2521

2622
This project is licensed under the GPL as it uses the [Mongoose Embedded Networking Library](https://github.com/cesanta/mongoose). If you have a proprietary license of Mongoose, then the [MIT License](https://github.com/matth-x/MicroOcpp/blob/master/LICENSE) applies.

src/MicroOcppMongooseClient.cpp

Lines changed: 163 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// GPL-3.0 License (see LICENSE)
44

55
#include "MicroOcppMongooseClient.h"
6-
#include "base64.hpp"
76
#include <MicroOcpp/Core/Configuration.h>
87
#include <MicroOcpp/Debug.h>
98

@@ -14,14 +13,18 @@
1413
#define MO_MG_F_IS_MOcppMongooseClient MG_F_USER_2
1514
#endif
1615

16+
namespace MicroOcpp {
17+
bool validateAuthorizationKeyHex(const char *auth_key_hex);
18+
}
19+
1720
using namespace MicroOcpp;
1821

1922
void ws_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data);
2023

2124
MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr,
2225
const char *backend_url_factory,
2326
const char *charge_box_id_factory,
24-
const char *auth_key_factory,
27+
unsigned char *auth_key_factory, size_t auth_key_factory_len,
2528
const char *ca_certificate,
2629
std::shared_ptr<FilesystemAdapter> filesystem,
2730
ProtocolVersion protocolVersion) : mgr(mgr), protocolVersion(protocolVersion) {
@@ -43,8 +46,22 @@ MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr,
4346
MO_CONFIG_EXT_PREFIX "BackendUrl", backend_url_factory, MO_WSCONN_FN, readonly, true);
4447
setting_cb_id_str = declareConfiguration<const char*>(
4548
MO_CONFIG_EXT_PREFIX "ChargeBoxId", charge_box_id_factory, MO_WSCONN_FN, readonly, true);
46-
setting_auth_key_str = declareConfiguration<const char*>(
47-
"AuthorizationKey", auth_key_factory, MO_WSCONN_FN, readonly, true);
49+
50+
if (auth_key_factory_len > MO_AUTHKEY_LEN_MAX) {
51+
MO_DBG_WARN("auth_key_factory too long - will be cropped");
52+
auth_key_factory_len = MO_AUTHKEY_LEN_MAX;
53+
}
54+
char auth_key_hex [2 * MO_AUTHKEY_LEN_MAX + 1];
55+
auth_key_hex[0] = '\0';
56+
if (auth_key_factory) {
57+
for (size_t i = 0; i < auth_key_factory_len; i += 2) {
58+
snprintf(auth_key_hex + 2 * i, 3, "%02X", auth_key_factory[i]);
59+
}
60+
}
61+
setting_auth_key_hex_str = declareConfiguration<const char*>(
62+
"AuthorizationKey", auth_key_hex, MO_WSCONN_FN, readonly, true);
63+
registerConfigurationValidator("AuthorizationKey", validateAuthorizationKeyHex);
64+
4865
ws_ping_interval_int = declareConfiguration<int>(
4966
"WebSocketPingInterval", 5, MO_WSCONN_FN);
5067
reconnect_interval_int = declareConfiguration<int>(
@@ -67,6 +84,24 @@ MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr,
6784
maintainWsConn();
6885
}
6986

87+
MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr,
88+
const char *backend_url_factory,
89+
const char *charge_box_id_factory,
90+
const char *auth_key_factory,
91+
const char *ca_certificate,
92+
std::shared_ptr<FilesystemAdapter> filesystem,
93+
ProtocolVersion protocolVersion) :
94+
95+
MOcppMongooseClient(mgr,
96+
backend_url_factory,
97+
charge_box_id_factory,
98+
(unsigned char *)auth_key_factory, auth_key_factory ? strlen(auth_key_factory) : 0,
99+
ca_certificate,
100+
filesystem,
101+
protocolVersion) {
102+
103+
}
104+
70105
MOcppMongooseClient::~MOcppMongooseClient() {
71106
MO_DBG_DEBUG("destruct MOcppMongooseClient");
72107
if (websocket) {
@@ -156,6 +191,59 @@ void MOcppMongooseClient::maintainWsConn() {
156191

157192
last_reconnection_attempt = mocpp_tick_ms();
158193

194+
/*
195+
* determine auth token
196+
*/
197+
198+
std::string basic_auth64;
199+
200+
if (auth_key_len > 0) {
201+
202+
#if MO_DBG_LEVEL >= MO_DL_DEBUG
203+
{
204+
char auth_key_hex [2 * MO_AUTHKEY_LEN_MAX + 1];
205+
auth_key_hex[0] = '\0';
206+
for (size_t i = 0; i < auth_key_len; i++) {
207+
snprintf(auth_key_hex + 2 * i, 3, "%02X", auth_key[i]);
208+
}
209+
MO_DBG_DEBUG("auth Token=%s:%s (key will be converted to non-hex)", cb_id.c_str(), auth_key_hex);
210+
}
211+
#endif //MO_DBG_LEVEL >= MO_DL_DEBUG
212+
213+
unsigned char *token = new unsigned char[cb_id.length() + 1 + auth_key_len]; //cb_id:auth_key
214+
if (!token) {
215+
//OOM
216+
return;
217+
}
218+
size_t len = 0;
219+
memcpy(token, cb_id.c_str(), cb_id.length());
220+
len += cb_id.length();
221+
token[len++] = (unsigned char) ':';
222+
memcpy(token + len, auth_key, auth_key_len);
223+
len += auth_key_len;
224+
225+
int base64_length = ((len + 2) / 3) * 4; //3 bytes base256 get encoded into 4 bytes base64. --> base64_len = ceil(len/3) * 4
226+
char *base64 = new char[base64_length + 1];
227+
if (!base64) {
228+
//OOM
229+
delete[] token;
230+
return;
231+
}
232+
233+
// mg_base64_encode() places a null terminator automatically, because the output is a c-string
234+
mg_base64_encode(token, len, base64);
235+
delete[] token;
236+
237+
MO_DBG_DEBUG("auth64 len=%u, auth64 Token=%s", base64_length, base64);
238+
239+
basic_auth64 = &base64[0];
240+
241+
delete[] base64;
242+
} else {
243+
MO_DBG_DEBUG("no authentication");
244+
(void) 0;
245+
}
246+
159247
#if defined(MO_MG_VERSION_614)
160248

161249
struct mg_connect_opts opts;
@@ -177,7 +265,7 @@ void MOcppMongooseClient::maintainWsConn() {
177265

178266
char extra_headers [128] = {'\0'};
179267

180-
if (!auth_key.empty()) {
268+
if (!basic_auth64.empty()) {
181269
auto ret = snprintf(extra_headers, 128, "Authorization: Basic %s\r\n", basic_auth64.c_str());
182270
if (ret < 0 || ret >= 128) {
183271
MO_DBG_ERR("Basic Authentication failed: %d", ret);
@@ -258,8 +346,23 @@ void MOcppMongooseClient::setAuthKey(const char *auth_key_cstr) {
258346
return;
259347
}
260348

261-
if (setting_auth_key_str) {
262-
setting_auth_key_str->setString(auth_key_cstr);
349+
return setAuthKey((const unsigned char*)auth_key_cstr, strlen(auth_key_cstr));
350+
}
351+
352+
void MOcppMongooseClient::setAuthKey(const unsigned char *auth_key, size_t len) {
353+
if (!auth_key || len > MO_AUTHKEY_LEN_MAX) {
354+
MO_DBG_ERR("invalid argument");
355+
return;
356+
}
357+
358+
char auth_key_hex [2 * MO_AUTHKEY_LEN_MAX + 1];
359+
auth_key_hex[0] = '\0';
360+
for (size_t i = 0; i < len; i += 2) {
361+
snprintf(auth_key_hex + 2 * i, 3, "%02X", auth_key[i]);
362+
}
363+
364+
if (setting_auth_key_hex_str) {
365+
setting_auth_key_hex_str->setString(auth_key_hex);
263366
configuration_save();
264367
}
265368
}
@@ -283,48 +386,46 @@ void MOcppMongooseClient::reloadConfigs() {
283386
cb_id = setting_cb_id_str->getString();
284387
}
285388

286-
if (setting_auth_key_str) {
287-
auth_key = setting_auth_key_str->getString();
389+
if (setting_auth_key_hex_str) {
390+
auto auth_key_hex = setting_auth_key_hex_str->getString();
391+
392+
#if MO_MG_VERSION_614
393+
cs_from_hex((char*)auth_key, auth_key_hex, strlen(auth_key_hex));
394+
#else
395+
mg_unhex(auth_key_hex, strlen(auth_key_hex), auth_key);
396+
#endif
397+
398+
auth_key_len = strlen(setting_auth_key_hex_str->getString()) / 2;
399+
auth_key[auth_key_len] = '\0'; //need null-termination as long as deprecated `const char *getAuthKey()` exists
288400
}
289401

290402
/*
291-
* determine new URL and auth token with updated WS credentials
403+
* determine new URL with updated WS credentials
292404
*/
293405

294406
url.clear();
295-
basic_auth64.clear();
296407

297408
if (backend_url.empty()) {
298409
MO_DBG_DEBUG("empty URL closes connection");
299410
return;
300-
} else {
301-
url = backend_url;
302-
303-
if (url.back() != '/' && !cb_id.empty()) {
304-
url.append("/");
305-
}
306-
307-
url.append(cb_id);
308411
}
309412

310-
if (!auth_key.empty()) {
311-
std::string token = cb_id + ":" + auth_key;
312-
313-
MO_DBG_DEBUG("auth Token=%s", token.c_str());
413+
url = backend_url;
314414

315-
unsigned int base64_length = encode_base64_length(token.length());
316-
std::vector<unsigned char> base64 (base64_length + 1);
317-
318-
// encode_base64() places a null terminator automatically, because the output is a string
319-
base64_length = encode_base64((const unsigned char*) token.c_str(), token.length(), &base64[0]);
320-
321-
MO_DBG_DEBUG("auth64 len=%u, auth64 Token=%s", base64_length, &base64[0]);
415+
if (url.back() != '/' && !cb_id.empty()) {
416+
url.append("/");
417+
}
418+
url.append(cb_id);
419+
}
322420

323-
basic_auth64 = (const char*) &base64[0];
324-
} else {
325-
MO_DBG_DEBUG("no authentication");
326-
(void) 0;
421+
int MOcppMongooseClient::printAuthKey(unsigned char *buf, size_t size) {
422+
if (!buf || size < auth_key_len) {
423+
MO_DBG_ERR("invalid argument");
424+
return -1;
327425
}
426+
427+
memcpy(buf, auth_key, auth_key_len);
428+
return (int)auth_key_len;
328429
}
329430

330431
void MOcppMongooseClient::setConnectionOpen(bool open) {
@@ -474,3 +575,31 @@ void ws_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
474575
}
475576
}
476577
#endif
578+
579+
bool MicroOcpp::validateAuthorizationKeyHex(const char *auth_key_hex) {
580+
if (!auth_key_hex) {
581+
return true; //nullptr (or "") means disable Auth
582+
}
583+
bool valid = true;
584+
size_t i = 0;
585+
while (i <= 2 * MO_AUTHKEY_LEN_MAX && auth_key_hex[i] != '\0') {
586+
//check if character is in 0-9, a-f, or A-F
587+
if ( (auth_key_hex[i] >= '0' && auth_key_hex[i] <= '9') ||
588+
(auth_key_hex[i] >= 'a' && auth_key_hex[i] <= 'f') ||
589+
(auth_key_hex[i] >= 'A' && auth_key_hex[i] <= 'F')) {
590+
//yes, it is
591+
i++;
592+
} else {
593+
//no, it isn't
594+
valid = false;
595+
break;
596+
}
597+
}
598+
valid &= auth_key_hex[i] == '\0';
599+
valid &= (i % 2) == 0;
600+
if (!valid) {
601+
MO_DBG_ERR("AuthorizationKey must be hex with at most 20 octets");
602+
(void)0;
603+
}
604+
return valid;
605+
}

src/MicroOcppMongooseClient.h

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#define MO_WSCONN_FN (MO_FILENAME_PREFIX "ws-conn.jsn")
2222
#endif
2323

24+
#define MO_AUTHKEY_LEN_MAX 20 //AuthKey in Bytes. Hex value has double length
25+
2426
namespace MicroOcpp {
2527

2628
class FilesystemAdapter;
@@ -33,12 +35,12 @@ class MOcppMongooseClient : public MicroOcpp::Connection {
3335
std::string backend_url;
3436
std::string cb_id;
3537
std::string url; //url = backend_url + '/' + cb_id
36-
std::string auth_key;
37-
std::string basic_auth64;
38+
unsigned char auth_key [MO_AUTHKEY_LEN_MAX + 1]; //AuthKey in bytes encoding ("FF01" = {0xFF, 0x01})
39+
size_t auth_key_len;
3840
const char *ca_cert; //zero-copy. The host system must ensure that this pointer remains valid during the lifetime of this class
3941
std::shared_ptr<Configuration> setting_backend_url_str;
4042
std::shared_ptr<Configuration> setting_cb_id_str;
41-
std::shared_ptr<Configuration> setting_auth_key_str;
43+
std::shared_ptr<Configuration> setting_auth_key_hex_str;
4244
unsigned long last_status_dbg_msg {0}, last_recv {0};
4345
std::shared_ptr<Configuration> reconnect_interval_int; //minimum time between two connect trials in s
4446
unsigned long last_reconnection_attempt {-1UL / 2UL};
@@ -57,6 +59,15 @@ class MOcppMongooseClient : public MicroOcpp::Connection {
5759
void maintainWsConn();
5860

5961
public:
62+
MOcppMongooseClient(struct mg_mgr *mgr,
63+
const char *backend_url_factory,
64+
const char *charge_box_id_factory,
65+
unsigned char *auth_key_factory, size_t auth_key_factory_len,
66+
const char *ca_cert = nullptr, //zero-copy, the string must outlive this class and mg_mgr. Forwards this string to Mongoose as ssl_ca_cert (see https://github.com/cesanta/mongoose/blob/ab650ec5c99ceb52bb9dc59e8e8ec92a2724932b/mongoose.h#L4192)
67+
std::shared_ptr<MicroOcpp::FilesystemAdapter> filesystem = nullptr,
68+
ProtocolVersion protocolVersion = ProtocolVersion(1,6));
69+
70+
//DEPRECATED: will be removed in a future release
6071
MOcppMongooseClient(struct mg_mgr *mgr,
6172
const char *backend_url_factory = nullptr,
6273
const char *charge_box_id_factory = nullptr,
@@ -82,14 +93,16 @@ class MOcppMongooseClient : public MicroOcpp::Connection {
8293
//update WS configs. To apply the updates, call `reloadConfigs()` afterwards
8394
void setBackendUrl(const char *backend_url);
8495
void setChargeBoxId(const char *cb_id);
85-
void setAuthKey(const char *auth_key);
96+
void setAuthKey(const char *auth_key); //DEPRECATED: will be removed in a future release
97+
void setAuthKey(const unsigned char *auth_key, size_t len); //set the auth key in bytes-encoded format
8698
void setCaCert(const char *ca_cert); //forwards this string to Mongoose as ssl_ca_cert (see https://github.com/cesanta/mongoose/blob/ab650ec5c99ceb52bb9dc59e8e8ec92a2724932b/mongoose.h#L4192)
8799

88100
void reloadConfigs();
89101

90102
const char *getBackendUrl() {return backend_url.c_str();}
91103
const char *getChargeBoxId() {return cb_id.c_str();}
92-
const char *getAuthKey() {return auth_key.c_str();}
104+
const char *getAuthKey() {return (const char*)auth_key;} //DEPRECATED: will be removed in a future release
105+
int printAuthKey(unsigned char *buf, size_t size);
93106
const char *getCaCert() {return ca_cert ? ca_cert : "";}
94107

95108
const char *getUrl() {return url.c_str();}

src/MicroOcppMongooseClient_c.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// matth-x/MicroOcppMongoose
2-
// Copyright Matthias Akstaller 2019 - 2023
2+
// Copyright Matthias Akstaller 2019 - 2024
33
// GPL-3.0 License (see LICENSE)
44

55
#include "MicroOcppMongooseClient_c.h"

0 commit comments

Comments
 (0)