Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change AuthorizationKey encoding to hex #4

Merged
merged 5 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Changelog

## [Unreleased]
## [v1.1.0] - 2024-05-21

### Changed

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

### Added

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

### Fixed

- Encode AuthorizationKey in Hex ([#4](https://github.com/matth-x/MicroOcppMongoose/pull/4))

## [1.0.0] - 2023-10-20

_First release._
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# MicroOcppMongoose
Mongoose WebSocket adapter for MicroOcpp

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

## Dependencies

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

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!

## License

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.
197 changes: 163 additions & 34 deletions src/MicroOcppMongooseClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// GPL-3.0 License (see LICENSE)

#include "MicroOcppMongooseClient.h"
#include "base64.hpp"
#include <MicroOcpp/Core/Configuration.h>
#include <MicroOcpp/Debug.h>

Expand All @@ -14,14 +13,18 @@
#define MO_MG_F_IS_MOcppMongooseClient MG_F_USER_2
#endif

namespace MicroOcpp {
bool validateAuthorizationKeyHex(const char *auth_key_hex);
}

using namespace MicroOcpp;

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

MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr,
const char *backend_url_factory,
const char *charge_box_id_factory,
const char *auth_key_factory,
unsigned char *auth_key_factory, size_t auth_key_factory_len,
const char *ca_certificate,
std::shared_ptr<FilesystemAdapter> filesystem,
ProtocolVersion protocolVersion) : mgr(mgr), protocolVersion(protocolVersion) {
Expand All @@ -43,8 +46,22 @@ MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr,
MO_CONFIG_EXT_PREFIX "BackendUrl", backend_url_factory, MO_WSCONN_FN, readonly, true);
setting_cb_id_str = declareConfiguration<const char*>(
MO_CONFIG_EXT_PREFIX "ChargeBoxId", charge_box_id_factory, MO_WSCONN_FN, readonly, true);
setting_auth_key_str = declareConfiguration<const char*>(
"AuthorizationKey", auth_key_factory, MO_WSCONN_FN, readonly, true);

if (auth_key_factory_len > MO_AUTHKEY_LEN_MAX) {
MO_DBG_WARN("auth_key_factory too long - will be cropped");
auth_key_factory_len = MO_AUTHKEY_LEN_MAX;
}
char auth_key_hex [2 * MO_AUTHKEY_LEN_MAX + 1];
auth_key_hex[0] = '\0';
if (auth_key_factory) {
for (size_t i = 0; i < auth_key_factory_len; i += 2) {
snprintf(auth_key_hex + 2 * i, 3, "%02X", auth_key_factory[i]);
}
}
setting_auth_key_hex_str = declareConfiguration<const char*>(
"AuthorizationKey", auth_key_hex, MO_WSCONN_FN, readonly, true);
registerConfigurationValidator("AuthorizationKey", validateAuthorizationKeyHex);

ws_ping_interval_int = declareConfiguration<int>(
"WebSocketPingInterval", 5, MO_WSCONN_FN);
reconnect_interval_int = declareConfiguration<int>(
Expand All @@ -67,6 +84,24 @@ MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr,
maintainWsConn();
}

MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr,
const char *backend_url_factory,
const char *charge_box_id_factory,
const char *auth_key_factory,
const char *ca_certificate,
std::shared_ptr<FilesystemAdapter> filesystem,
ProtocolVersion protocolVersion) :

MOcppMongooseClient(mgr,
backend_url_factory,
charge_box_id_factory,
(unsigned char *)auth_key_factory, auth_key_factory ? strlen(auth_key_factory) : 0,
ca_certificate,
filesystem,
protocolVersion) {

}

MOcppMongooseClient::~MOcppMongooseClient() {
MO_DBG_DEBUG("destruct MOcppMongooseClient");
if (websocket) {
Expand Down Expand Up @@ -156,6 +191,59 @@ void MOcppMongooseClient::maintainWsConn() {

last_reconnection_attempt = mocpp_tick_ms();

/*
* determine auth token
*/

std::string basic_auth64;

if (auth_key_len > 0) {

#if MO_DBG_LEVEL >= MO_DL_DEBUG
{
char auth_key_hex [2 * MO_AUTHKEY_LEN_MAX + 1];
auth_key_hex[0] = '\0';
for (size_t i = 0; i < auth_key_len; i++) {
snprintf(auth_key_hex + 2 * i, 3, "%02X", auth_key[i]);
}
MO_DBG_DEBUG("auth Token=%s:%s (key will be converted to non-hex)", cb_id.c_str(), auth_key_hex);
}
#endif //MO_DBG_LEVEL >= MO_DL_DEBUG

unsigned char *token = new unsigned char[cb_id.length() + 1 + auth_key_len]; //cb_id:auth_key
if (!token) {
//OOM
return;
}
size_t len = 0;
memcpy(token, cb_id.c_str(), cb_id.length());
len += cb_id.length();
token[len++] = (unsigned char) ':';
memcpy(token + len, auth_key, auth_key_len);
len += auth_key_len;

int base64_length = ((len + 2) / 3) * 4; //3 bytes base256 get encoded into 4 bytes base64. --> base64_len = ceil(len/3) * 4
char *base64 = new char[base64_length + 1];
if (!base64) {
//OOM
delete[] token;
return;
}

// mg_base64_encode() places a null terminator automatically, because the output is a c-string
mg_base64_encode(token, len, base64);
delete[] token;

MO_DBG_DEBUG("auth64 len=%u, auth64 Token=%s", base64_length, base64);

basic_auth64 = &base64[0];

delete[] base64;
} else {
MO_DBG_DEBUG("no authentication");
(void) 0;
}

#if defined(MO_MG_VERSION_614)

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

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

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

if (setting_auth_key_str) {
setting_auth_key_str->setString(auth_key_cstr);
return setAuthKey((const unsigned char*)auth_key_cstr, strlen(auth_key_cstr));
}

void MOcppMongooseClient::setAuthKey(const unsigned char *auth_key, size_t len) {
if (!auth_key || len > MO_AUTHKEY_LEN_MAX) {
MO_DBG_ERR("invalid argument");
return;
}

char auth_key_hex [2 * MO_AUTHKEY_LEN_MAX + 1];
auth_key_hex[0] = '\0';
for (size_t i = 0; i < len; i += 2) {
snprintf(auth_key_hex + 2 * i, 3, "%02X", auth_key[i]);
}

if (setting_auth_key_hex_str) {
setting_auth_key_hex_str->setString(auth_key_hex);
configuration_save();
}
}
Expand All @@ -283,48 +386,46 @@ void MOcppMongooseClient::reloadConfigs() {
cb_id = setting_cb_id_str->getString();
}

if (setting_auth_key_str) {
auth_key = setting_auth_key_str->getString();
if (setting_auth_key_hex_str) {
auto auth_key_hex = setting_auth_key_hex_str->getString();

#if MO_MG_VERSION_614
cs_from_hex((char*)auth_key, auth_key_hex, strlen(auth_key_hex));
#else
mg_unhex(auth_key_hex, strlen(auth_key_hex), auth_key);
#endif

auth_key_len = strlen(setting_auth_key_hex_str->getString()) / 2;
auth_key[auth_key_len] = '\0'; //need null-termination as long as deprecated `const char *getAuthKey()` exists
}

/*
* determine new URL and auth token with updated WS credentials
* determine new URL with updated WS credentials
*/

url.clear();
basic_auth64.clear();

if (backend_url.empty()) {
MO_DBG_DEBUG("empty URL closes connection");
return;
} else {
url = backend_url;

if (url.back() != '/' && !cb_id.empty()) {
url.append("/");
}

url.append(cb_id);
}

if (!auth_key.empty()) {
std::string token = cb_id + ":" + auth_key;

MO_DBG_DEBUG("auth Token=%s", token.c_str());
url = backend_url;

unsigned int base64_length = encode_base64_length(token.length());
std::vector<unsigned char> base64 (base64_length + 1);

// encode_base64() places a null terminator automatically, because the output is a string
base64_length = encode_base64((const unsigned char*) token.c_str(), token.length(), &base64[0]);

MO_DBG_DEBUG("auth64 len=%u, auth64 Token=%s", base64_length, &base64[0]);
if (url.back() != '/' && !cb_id.empty()) {
url.append("/");
}
url.append(cb_id);
}

basic_auth64 = (const char*) &base64[0];
} else {
MO_DBG_DEBUG("no authentication");
(void) 0;
int MOcppMongooseClient::printAuthKey(unsigned char *buf, size_t size) {
if (!buf || size < auth_key_len) {
MO_DBG_ERR("invalid argument");
return -1;
}

memcpy(buf, auth_key, auth_key_len);
return (int)auth_key_len;
}

void MOcppMongooseClient::setConnectionOpen(bool open) {
Expand Down Expand Up @@ -474,3 +575,31 @@ void ws_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
}
}
#endif

bool MicroOcpp::validateAuthorizationKeyHex(const char *auth_key_hex) {
if (!auth_key_hex) {
return true; //nullptr (or "") means disable Auth
}
bool valid = true;
size_t i = 0;
while (i <= 2 * MO_AUTHKEY_LEN_MAX && auth_key_hex[i] != '\0') {
//check if character is in 0-9, a-f, or A-F
if ( (auth_key_hex[i] >= '0' && auth_key_hex[i] <= '9') ||
(auth_key_hex[i] >= 'a' && auth_key_hex[i] <= 'f') ||
(auth_key_hex[i] >= 'A' && auth_key_hex[i] <= 'F')) {
//yes, it is
i++;
} else {
//no, it isn't
valid = false;
break;
}
}
valid &= auth_key_hex[i] == '\0';
valid &= (i % 2) == 0;
if (!valid) {
MO_DBG_ERR("AuthorizationKey must be hex with at most 20 octets");
(void)0;
}
return valid;
}
23 changes: 18 additions & 5 deletions src/MicroOcppMongooseClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#define MO_WSCONN_FN (MO_FILENAME_PREFIX "ws-conn.jsn")
#endif

#define MO_AUTHKEY_LEN_MAX 20 //AuthKey in Bytes. Hex value has double length

namespace MicroOcpp {

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

public:
MOcppMongooseClient(struct mg_mgr *mgr,
const char *backend_url_factory,
const char *charge_box_id_factory,
unsigned char *auth_key_factory, size_t auth_key_factory_len,
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)
std::shared_ptr<MicroOcpp::FilesystemAdapter> filesystem = nullptr,
ProtocolVersion protocolVersion = ProtocolVersion(1,6));

//DEPRECATED: will be removed in a future release
MOcppMongooseClient(struct mg_mgr *mgr,
const char *backend_url_factory = nullptr,
const char *charge_box_id_factory = nullptr,
Expand All @@ -82,14 +93,16 @@ class MOcppMongooseClient : public MicroOcpp::Connection {
//update WS configs. To apply the updates, call `reloadConfigs()` afterwards
void setBackendUrl(const char *backend_url);
void setChargeBoxId(const char *cb_id);
void setAuthKey(const char *auth_key);
void setAuthKey(const char *auth_key); //DEPRECATED: will be removed in a future release
void setAuthKey(const unsigned char *auth_key, size_t len); //set the auth key in bytes-encoded format
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)

void reloadConfigs();

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

const char *getUrl() {return url.c_str();}
Expand Down
2 changes: 1 addition & 1 deletion src/MicroOcppMongooseClient_c.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// matth-x/MicroOcppMongoose
// Copyright Matthias Akstaller 2019 - 2023
// Copyright Matthias Akstaller 2019 - 2024
// GPL-3.0 License (see LICENSE)

#include "MicroOcppMongooseClient_c.h"
Expand Down
Loading