Skip to content
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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ The primary goal is to make the parser independent of the Arduino framework and
* Combines all fixes from [matthijskooijman/arduino-dsmr](https://github.com/matthijskooijman/arduino-dsmr) and [glmnet/arduino-dsmr](https://github.com/glmnet/arduino-dsmr) including unmerged pull requests.
* Added an extensive unit test suite
* Code optimizations
* No dynamic memory allocations
* Supported compilers: MSVC, GCC, Clang
* Header-only library, no dependencies
* Code can be used on any platform, not only embedded.
* Code can be used on any platform, not only embedded

# Differences from the original arduino-dsmr
* Requires a C++20 compatible compiler.
Expand All @@ -17,20 +18,19 @@ The primary goal is to make the parser independent of the Arduino framework and
# How to use
## General usage
The library is header-only. Add the `src/dsmr_parser` folder to your project.<br>
Note: [dlms_packet_decryptor.h](https://github.com/esphome-libs/dsmr_parser/blob/main/src/dsmr_parser/dlms_packet_decryptor.h) depends on [Mbed TLS](https://www.trustedfirmware.org/projects/mbed-tls/) or [BearSsl](https://bearssl.org/) library. `Mbed TLS` is already included in the `ESP-IDF` framework and can be easily added to any other platforms.
Note: [dlms_packet_decryptor.h](https://github.com/esphome-libs/dsmr_parser/blob/main/src/dsmr_parser/dlms_packet_decryptor.h) requires one of the encryption libraries: [TF-PSA](https://github.com/Mbed-TLS/TF-PSA-Crypto), [Mbed TLS](https://github.com/Mbed-TLS/mbedtls) or [BearSsl](https://bearssl.org/).

## Usage from PlatformIO
The library is available on the PlatformIO registry:<br>
[esphome/dsmr_parser](https://registry.platformio.org/libraries/esphome/dsmr_parser)

# Examples
* How to use the parser
* [minimal_parse.ino](https://github.com/matthijskooijman/arduino-dsmr/blob/master/examples/minimal_parse/minimal_parse.ino)
* [parse.ino](https://github.com/matthijskooijman/arduino-dsmr/blob/master/examples/parse/parse.ino)
* Complete example using PacketAccumulator
* [packet_accumulator_example_test.cpp](https://github.com/esphome-libs/dsmr_parser/blob/main/src/test/packet_accumulator_example_test.cpp)
* Example using DlmsPacketDecryptor
* [dlms_packet_decryptor_example_test.cpp](https://github.com/esphome-libs/dsmr_parser/blob/main/src/test/dlms_packet_decryptor_example_test.cpp)
* Usage in EspHome project
* [DSMR component](https://github.com/esphome/esphome/tree/dev/esphome/components/dsmr)

# History behind arduino-dsmr
[matthijskooijman](https://github.com/matthijskooijman) is the original creator of this DSMR parser.
Expand Down
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dsmr_parser",
"version": "1.2.0",
"version": "1.3.0",
"description": "A parser for Dutch Smart Meter Requirements (DSMR) telegrams. Fork of arduino-dsmr. Doesn't depend on the Arduino framework and has many bug fixes and code quality improvements. Supports encrypted DSMR packets.",
"keywords": "dsmr",
"repository": {
Expand Down
47 changes: 0 additions & 47 deletions src/dsmr_parser/aes128gcm.h

This file was deleted.

41 changes: 41 additions & 0 deletions src/dsmr_parser/decryption/aes128gcm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once
#include "../util.h"
#include <charconv>
#include <optional>
#include <span>
Comment thread
PolarGoose marked this conversation as resolved.

namespace dsmr_parser {

class Aes128GcmDecryptionKey final {
std::array<uint8_t, 16> key{};
explicit Aes128GcmDecryptionKey(const std::array<uint8_t, 16> k) : key(k) {}

public:
// hex is a string like "00112233445566778899AABBCCDDEEFF"
static std::optional<Aes128GcmDecryptionKey> from_hex(const std::string_view hex) {
if (hex.size() != 32)
return std::nullopt;
std::array<uint8_t, 16> arr{};
for (size_t i = 0; i < 16; ++i) {
auto [ptr, ec] = std::from_chars(hex.data() + i * 2, hex.data() + i * 2 + 2, arr[i], 16);
if (ec != std::errc{})
return std::nullopt;
}
return Aes128GcmDecryptionKey(arr);
}

const uint8_t* data() const { return key.data(); }
};

class Aes128GcmDecryptor {
public:
virtual void set_encryption_key(const Aes128GcmDecryptionKey& key) = 0;
virtual bool decrypt_inplace(std::span<const uint8_t, 17> aad, std::span<const uint8_t, 12> nonce, std::span<uint8_t> ciphertext,
std::span<const uint8_t, 12> tag) = 0;

protected:
virtual ~Aes128GcmDecryptor() = default;
std::optional<Aes128GcmDecryptionKey> decryption_key_;
};

}
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
#pragma once
#include "aes128gcm.h"
#include "util.h"

#if __has_include(<bearssl/bearssl.h>)
#include <bearssl/bearssl.h>
#elif __has_include(<bearssl.h>)
#include <bearssl.h>
#else
#error "BearSSL header not found"
#endif

#include "../util.h"
#include "aes128gcm.h"
#include <span>

namespace dsmr_parser {

class Aes128GcmBearSsl final : NonCopyableAndNonMovable {
class Aes128GcmBearSsl final : public Aes128GcmDecryptor, NonCopyableAndNonMovable {
br_gcm_context gcm;
br_aes_ct_ctr_keys aes;
bool initialized = false;

public:
Aes128GcmBearSsl() = default;

void set_encryption_key(const Aes128GcmEncryptionKey& key) {
void set_encryption_key(const Aes128GcmDecryptionKey& key) override {
br_aes_ct_ctr_init(&aes, key.data(), 16);
br_gcm_init(&gcm, &aes.vtable, br_ghash_ctmul32);
initialized = true;
}

bool decrypt_inplace(const std::array<const uint8_t, 17>& aad, const std::array<const uint8_t, 12>& nonce, std::span<uint8_t> ciphertext,
const std::array<const uint8_t, 12>& tag) {
bool decrypt_inplace(std::span<const uint8_t, 17> aad, std::span<const uint8_t, 12> nonce, std::span<uint8_t> ciphertext,
std::span<const uint8_t, 12> tag) override {
if (!initialized) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
#pragma once
#include "../util.h"
#include "aes128gcm.h"
#include "util.h"
#include <mbedtls/gcm.h>
#include <span>

namespace dsmr_parser {

class Aes128GcmMbedTls final : NonCopyableAndNonMovable {
class Aes128GcmMbedTls final : public Aes128GcmDecryptor, NonCopyableAndNonMovable {
mbedtls_gcm_context gcm;

public:
Aes128GcmMbedTls() { mbedtls_gcm_init(&gcm); }

void set_encryption_key(const Aes128GcmEncryptionKey& key) { mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key.data(), 128); }
void set_encryption_key(const Aes128GcmDecryptionKey& key) override { mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key.data(), 128); }

bool decrypt_inplace(const std::array<const uint8_t, 17>& aad, const std::array<const uint8_t, 12>& nonce, std::span<uint8_t> ciphertext,
const std::array<const uint8_t, 12>& tag) {
bool decrypt_inplace(std::span<const uint8_t, 17> aad, std::span<const uint8_t, 12> nonce, std::span<uint8_t> ciphertext,
std::span<const uint8_t, 12> tag) override {
const auto& res = mbedtls_gcm_auth_decrypt(&gcm, ciphertext.size(), nonce.data(), nonce.size(), aad.data(), aad.size(), tag.data(), tag.size(),
ciphertext.data(), ciphertext.data());
return res == 0;
Expand Down
98 changes: 98 additions & 0 deletions src/dsmr_parser/decryption/aes128gcm_tfpsa.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#pragma once

#include "../util.h"
#include "aes128gcm.h"
#include <psa/crypto.h>
#include <span>

Comment thread
PolarGoose marked this conversation as resolved.
namespace dsmr_parser {

class Aes128GcmTfPsa final : public Aes128GcmDecryptor, NonCopyableAndNonMovable {
psa_key_id_t key_id = 0;
bool initialized = false;

public:
Aes128GcmTfPsa() { initialized = (psa_crypto_init() == PSA_SUCCESS); }

void set_encryption_key(const Aes128GcmDecryptionKey& key) override {
if (!initialized) {
return;
}

if (key_id != 0) {
psa_destroy_key(key_id);
key_id = 0;
}

psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attributes, 128);
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT);
psa_set_key_algorithm(&attributes, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, 12));

const psa_status_t status = psa_import_key(&attributes, key.data(), 16, &key_id);

psa_reset_key_attributes(&attributes);

if (status != PSA_SUCCESS) {
key_id = 0;
}
}

bool decrypt_inplace(std::span<const uint8_t, 17> aad, std::span<const uint8_t, 12> nonce, std::span<uint8_t> ciphertext,
std::span<const uint8_t, 12> tag) override {
if (!initialized || key_id == 0) {
return false;
}

psa_aead_operation_t op = PSA_AEAD_OPERATION_INIT;
size_t out_len = 0;
size_t tail_len = 0;

psa_status_t status = psa_aead_decrypt_setup(&op, key_id, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, tag.size()));
if (status != PSA_SUCCESS) {
psa_aead_abort(&op);
return false;
}

status = psa_aead_set_nonce(&op, nonce.data(), nonce.size());
if (status != PSA_SUCCESS) {
psa_aead_abort(&op);
return false;
}

status = psa_aead_update_ad(&op, aad.data(), aad.size());
if (status != PSA_SUCCESS) {
psa_aead_abort(&op);
return false;
}

status = psa_aead_update(&op, ciphertext.data(), ciphertext.size(), ciphertext.data(), ciphertext.size(), &out_len);
if (status != PSA_SUCCESS || out_len != ciphertext.size()) {
psa_aead_abort(&op);
return false;
}

status = psa_aead_verify(&op, nullptr, 0, &tail_len, tag.data(), tag.size());

if (status != PSA_SUCCESS) {
psa_aead_abort(&op);
return false;
}

if (tail_len != 0) {
psa_aead_abort(&op);
return false;
}

return true;
}

~Aes128GcmTfPsa() {
if (key_id != 0) {
psa_destroy_key(key_id);
}
}
};

} // namespace dsmr_parser
Loading
Loading