mirror of
https://github.com/esphome/esphome.git
synced 2025-10-01 17:42:22 +01:00
Add sha256 support (#10882)
This commit is contained in:
@@ -407,6 +407,7 @@ esphome/components/sensor/* @esphome/core
|
|||||||
esphome/components/sfa30/* @ghsensdev
|
esphome/components/sfa30/* @ghsensdev
|
||||||
esphome/components/sgp40/* @SenexCrenshaw
|
esphome/components/sgp40/* @SenexCrenshaw
|
||||||
esphome/components/sgp4x/* @martgras @SenexCrenshaw
|
esphome/components/sgp4x/* @martgras @SenexCrenshaw
|
||||||
|
esphome/components/sha256/* @esphome/core
|
||||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
||||||
esphome/components/sht3xd/* @mrtoy-me
|
esphome/components/sht3xd/* @mrtoy-me
|
||||||
esphome/components/sht4x/* @sjtrny
|
esphome/components/sht4x/* @sjtrny
|
||||||
|
@@ -39,32 +39,6 @@ void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_
|
|||||||
void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); }
|
void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); }
|
||||||
#endif // USE_RP2040
|
#endif // USE_RP2040
|
||||||
|
|
||||||
void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); }
|
|
||||||
|
|
||||||
void MD5Digest::get_hex(char *output) {
|
|
||||||
for (size_t i = 0; i < 16; i++) {
|
|
||||||
uint8_t byte = this->digest_[i];
|
|
||||||
output[i * 2] = format_hex_char(byte >> 4);
|
|
||||||
output[i * 2 + 1] = format_hex_char(byte & 0x0F);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MD5Digest::equals_bytes(const uint8_t *expected) {
|
|
||||||
for (size_t i = 0; i < 16; i++) {
|
|
||||||
if (expected[i] != this->digest_[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MD5Digest::equals_hex(const char *expected) {
|
|
||||||
uint8_t parsed[16];
|
|
||||||
if (!parse_hex(expected, parsed, 16))
|
|
||||||
return false;
|
|
||||||
return equals_bytes(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace md5
|
} // namespace md5
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
#endif
|
#endif
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#ifdef USE_MD5
|
#ifdef USE_MD5
|
||||||
|
|
||||||
|
#include "esphome/core/hash_base.h"
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#include "esp_rom_md5.h"
|
#include "esp_rom_md5.h"
|
||||||
#define MD5_CTX_TYPE md5_context_t
|
#define MD5_CTX_TYPE md5_context_t
|
||||||
@@ -26,38 +28,26 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace md5 {
|
namespace md5 {
|
||||||
|
|
||||||
class MD5Digest {
|
class MD5Digest : public HashBase {
|
||||||
public:
|
public:
|
||||||
MD5Digest() = default;
|
MD5Digest() = default;
|
||||||
~MD5Digest() = default;
|
~MD5Digest() override = default;
|
||||||
|
|
||||||
/// Initialize a new MD5 digest computation.
|
/// Initialize a new MD5 digest computation.
|
||||||
void init();
|
void init() override;
|
||||||
|
|
||||||
/// Add bytes of data for the digest.
|
/// Add bytes of data for the digest.
|
||||||
void add(const uint8_t *data, size_t len);
|
void add(const uint8_t *data, size_t len) override;
|
||||||
void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); }
|
using HashBase::add; // Bring base class overload into scope
|
||||||
|
|
||||||
/// Compute the digest, based on the provided data.
|
/// Compute the digest, based on the provided data.
|
||||||
void calculate();
|
void calculate() override;
|
||||||
|
|
||||||
/// Retrieve the MD5 digest as bytes.
|
/// Get the size of the hash in bytes (16 for MD5)
|
||||||
/// The output must be able to hold 16 bytes or more.
|
size_t get_size() const override { return 16; }
|
||||||
void get_bytes(uint8_t *output);
|
|
||||||
|
|
||||||
/// Retrieve the MD5 digest as hex characters.
|
|
||||||
/// The output must be able to hold 32 bytes or more.
|
|
||||||
void get_hex(char *output);
|
|
||||||
|
|
||||||
/// Compare the digest against a provided byte-encoded digest (16 bytes).
|
|
||||||
bool equals_bytes(const uint8_t *expected);
|
|
||||||
|
|
||||||
/// Compare the digest against a provided hex-encoded digest (32 bytes).
|
|
||||||
bool equals_hex(const char *expected);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MD5_CTX_TYPE ctx_{};
|
MD5_CTX_TYPE ctx_{};
|
||||||
uint8_t digest_[16];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace md5
|
} // namespace md5
|
||||||
|
22
esphome/components/sha256/__init__.py
Normal file
22
esphome/components/sha256/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.core import CORE
|
||||||
|
from esphome.helpers import IS_MACOS
|
||||||
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
|
sha256_ns = cg.esphome_ns.namespace("sha256")
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema({})
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config: ConfigType) -> None:
|
||||||
|
# Add OpenSSL library for host platform
|
||||||
|
if not CORE.is_host:
|
||||||
|
return
|
||||||
|
if IS_MACOS:
|
||||||
|
# macOS needs special handling for Homebrew OpenSSL
|
||||||
|
cg.add_build_flag("-I/opt/homebrew/opt/openssl/include")
|
||||||
|
cg.add_build_flag("-L/opt/homebrew/opt/openssl/lib")
|
||||||
|
cg.add_build_flag("-lcrypto")
|
83
esphome/components/sha256/sha256.cpp
Normal file
83
esphome/components/sha256/sha256.cpp
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#include "sha256.h"
|
||||||
|
|
||||||
|
// Only compile SHA256 implementation on platforms that support it
|
||||||
|
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
||||||
|
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace esphome::sha256 {
|
||||||
|
|
||||||
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
|
||||||
|
SHA256::~SHA256() { mbedtls_sha256_free(&this->ctx_); }
|
||||||
|
|
||||||
|
void SHA256::init() {
|
||||||
|
mbedtls_sha256_init(&this->ctx_);
|
||||||
|
mbedtls_sha256_starts(&this->ctx_, 0); // 0 = SHA256, not SHA224
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA256::add(const uint8_t *data, size_t len) { mbedtls_sha256_update(&this->ctx_, data, len); }
|
||||||
|
|
||||||
|
void SHA256::calculate() { mbedtls_sha256_finish(&this->ctx_, this->digest_); }
|
||||||
|
|
||||||
|
#elif defined(USE_ESP8266) || defined(USE_RP2040)
|
||||||
|
|
||||||
|
SHA256::~SHA256() = default;
|
||||||
|
|
||||||
|
void SHA256::init() {
|
||||||
|
br_sha256_init(&this->ctx_);
|
||||||
|
this->calculated_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA256::add(const uint8_t *data, size_t len) { br_sha256_update(&this->ctx_, data, len); }
|
||||||
|
|
||||||
|
void SHA256::calculate() {
|
||||||
|
if (!this->calculated_) {
|
||||||
|
br_sha256_out(&this->ctx_, this->digest_);
|
||||||
|
this->calculated_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_HOST)
|
||||||
|
|
||||||
|
SHA256::~SHA256() {
|
||||||
|
if (this->ctx_) {
|
||||||
|
EVP_MD_CTX_free(this->ctx_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA256::init() {
|
||||||
|
if (this->ctx_) {
|
||||||
|
EVP_MD_CTX_free(this->ctx_);
|
||||||
|
}
|
||||||
|
this->ctx_ = EVP_MD_CTX_new();
|
||||||
|
EVP_DigestInit_ex(this->ctx_, EVP_sha256(), nullptr);
|
||||||
|
this->calculated_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA256::add(const uint8_t *data, size_t len) {
|
||||||
|
if (!this->ctx_) {
|
||||||
|
this->init();
|
||||||
|
}
|
||||||
|
EVP_DigestUpdate(this->ctx_, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA256::calculate() {
|
||||||
|
if (!this->ctx_) {
|
||||||
|
this->init();
|
||||||
|
}
|
||||||
|
if (!this->calculated_) {
|
||||||
|
unsigned int len = 32;
|
||||||
|
EVP_DigestFinal_ex(this->ctx_, this->digest_, &len);
|
||||||
|
this->calculated_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error "SHA256 not supported on this platform"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace esphome::sha256
|
||||||
|
|
||||||
|
#endif // Platform check
|
56
esphome/components/sha256/sha256.h
Normal file
56
esphome/components/sha256/sha256.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
// Only define SHA256 on platforms that support it
|
||||||
|
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include "esphome/core/hash_base.h"
|
||||||
|
|
||||||
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
#include "mbedtls/sha256.h"
|
||||||
|
#elif defined(USE_ESP8266) || defined(USE_RP2040)
|
||||||
|
#include <bearssl/bearssl_hash.h>
|
||||||
|
#elif defined(USE_HOST)
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#else
|
||||||
|
#error "SHA256 not supported on this platform"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome::sha256 {
|
||||||
|
|
||||||
|
class SHA256 : public esphome::HashBase {
|
||||||
|
public:
|
||||||
|
SHA256() = default;
|
||||||
|
~SHA256() override;
|
||||||
|
|
||||||
|
void init() override;
|
||||||
|
void add(const uint8_t *data, size_t len) override;
|
||||||
|
using HashBase::add; // Bring base class overload into scope
|
||||||
|
void add(const std::string &data) { this->add((const uint8_t *) data.c_str(), data.length()); }
|
||||||
|
|
||||||
|
void calculate() override;
|
||||||
|
|
||||||
|
/// Get the size of the hash in bytes (32 for SHA256)
|
||||||
|
size_t get_size() const override { return 32; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
mbedtls_sha256_context ctx_{};
|
||||||
|
#elif defined(USE_ESP8266) || defined(USE_RP2040)
|
||||||
|
br_sha256_context ctx_{};
|
||||||
|
bool calculated_{false};
|
||||||
|
#elif defined(USE_HOST)
|
||||||
|
EVP_MD_CTX *ctx_{nullptr};
|
||||||
|
bool calculated_{false};
|
||||||
|
#else
|
||||||
|
#error "SHA256 not supported on this platform"
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::sha256
|
||||||
|
|
||||||
|
#endif // Platform check
|
@@ -116,6 +116,7 @@
|
|||||||
#define USE_API_PLAINTEXT
|
#define USE_API_PLAINTEXT
|
||||||
#define USE_API_SERVICES
|
#define USE_API_SERVICES
|
||||||
#define USE_MD5
|
#define USE_MD5
|
||||||
|
#define USE_SHA256
|
||||||
#define USE_MQTT
|
#define USE_MQTT
|
||||||
#define USE_NETWORK
|
#define USE_NETWORK
|
||||||
#define USE_ONLINE_IMAGE_BMP_SUPPORT
|
#define USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
|
56
esphome/core/hash_base.h
Normal file
56
esphome/core/hash_base.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
/// Base class for hash algorithms
|
||||||
|
class HashBase {
|
||||||
|
public:
|
||||||
|
virtual ~HashBase() = default;
|
||||||
|
|
||||||
|
/// Initialize a new hash computation
|
||||||
|
virtual void init() = 0;
|
||||||
|
|
||||||
|
/// Add bytes of data for the hash
|
||||||
|
virtual void add(const uint8_t *data, size_t len) = 0;
|
||||||
|
void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); }
|
||||||
|
|
||||||
|
/// Compute the hash based on provided data
|
||||||
|
virtual void calculate() = 0;
|
||||||
|
|
||||||
|
/// Retrieve the hash as bytes
|
||||||
|
void get_bytes(uint8_t *output) { memcpy(output, this->digest_, this->get_size()); }
|
||||||
|
|
||||||
|
/// Retrieve the hash as hex characters
|
||||||
|
void get_hex(char *output) {
|
||||||
|
for (size_t i = 0; i < this->get_size(); i++) {
|
||||||
|
uint8_t byte = this->digest_[i];
|
||||||
|
output[i * 2] = format_hex_char(byte >> 4);
|
||||||
|
output[i * 2 + 1] = format_hex_char(byte & 0x0F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare the hash against a provided byte-encoded hash
|
||||||
|
bool equals_bytes(const uint8_t *expected) { return memcmp(this->digest_, expected, this->get_size()) == 0; }
|
||||||
|
|
||||||
|
/// Compare the hash against a provided hex-encoded hash
|
||||||
|
bool equals_hex(const char *expected) {
|
||||||
|
uint8_t parsed[this->get_size()];
|
||||||
|
if (!parse_hex(expected, parsed, this->get_size())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this->equals_bytes(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the size of the hash in bytes (16 for MD5, 32 for SHA256)
|
||||||
|
virtual size_t get_size() const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t digest_[32]; // Storage sized for max(MD5=16, SHA256=32) bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome
|
@@ -82,6 +82,16 @@ template<typename T> constexpr T byteswap(T n) {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
template<> constexpr uint8_t byteswap(uint8_t n) { return n; }
|
template<> constexpr uint8_t byteswap(uint8_t n) { return n; }
|
||||||
|
#ifdef USE_LIBRETINY
|
||||||
|
// LibreTiny's Beken framework redefines __builtin_bswap functions as non-constexpr
|
||||||
|
template<> inline uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); }
|
||||||
|
template<> inline uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); }
|
||||||
|
template<> inline uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); }
|
||||||
|
template<> inline int8_t byteswap(int8_t n) { return n; }
|
||||||
|
template<> inline int16_t byteswap(int16_t n) { return __builtin_bswap16(n); }
|
||||||
|
template<> inline int32_t byteswap(int32_t n) { return __builtin_bswap32(n); }
|
||||||
|
template<> inline int64_t byteswap(int64_t n) { return __builtin_bswap64(n); }
|
||||||
|
#else
|
||||||
template<> constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); }
|
template<> constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); }
|
||||||
template<> constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); }
|
template<> constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); }
|
||||||
template<> constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); }
|
template<> constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); }
|
||||||
@@ -89,6 +99,7 @@ template<> constexpr int8_t byteswap(int8_t n) { return n; }
|
|||||||
template<> constexpr int16_t byteswap(int16_t n) { return __builtin_bswap16(n); }
|
template<> constexpr int16_t byteswap(int16_t n) { return __builtin_bswap16(n); }
|
||||||
template<> constexpr int32_t byteswap(int32_t n) { return __builtin_bswap32(n); }
|
template<> constexpr int32_t byteswap(int32_t n) { return __builtin_bswap32(n); }
|
||||||
template<> constexpr int64_t byteswap(int64_t n) { return __builtin_bswap64(n); }
|
template<> constexpr int64_t byteswap(int64_t n) { return __builtin_bswap64(n); }
|
||||||
|
#endif
|
||||||
|
|
||||||
///@}
|
///@}
|
||||||
|
|
||||||
|
32
tests/components/sha256/common.yaml
Normal file
32
tests/components/sha256/common.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
esphome:
|
||||||
|
on_boot:
|
||||||
|
- lambda: |-
|
||||||
|
// Test SHA256 functionality
|
||||||
|
#ifdef USE_SHA256
|
||||||
|
using esphome::sha256::SHA256;
|
||||||
|
SHA256 hasher;
|
||||||
|
hasher.init();
|
||||||
|
|
||||||
|
// Test with "Hello World" - known SHA256
|
||||||
|
const char* test_string = "Hello World";
|
||||||
|
hasher.add(test_string, strlen(test_string));
|
||||||
|
hasher.calculate();
|
||||||
|
|
||||||
|
char hex_output[65];
|
||||||
|
hasher.get_hex(hex_output);
|
||||||
|
hex_output[64] = '\0';
|
||||||
|
|
||||||
|
ESP_LOGD("SHA256", "SHA256('Hello World') = %s", hex_output);
|
||||||
|
|
||||||
|
// Expected: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e
|
||||||
|
const char* expected = "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e";
|
||||||
|
if (strcmp(hex_output, expected) == 0) {
|
||||||
|
ESP_LOGI("SHA256", "Test PASSED");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE("SHA256", "Test FAILED. Expected %s", expected);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ESP_LOGW("SHA256", "SHA256 not available on this platform");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
sha256:
|
1
tests/components/sha256/test.bk72xx-ard.yaml
Normal file
1
tests/components/sha256/test.bk72xx-ard.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common.yaml
|
1
tests/components/sha256/test.esp32-idf.yaml
Normal file
1
tests/components/sha256/test.esp32-idf.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common.yaml
|
1
tests/components/sha256/test.esp8266-ard.yaml
Normal file
1
tests/components/sha256/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common.yaml
|
1
tests/components/sha256/test.host.yaml
Normal file
1
tests/components/sha256/test.host.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common.yaml
|
1
tests/components/sha256/test.rp2040-ard.yaml
Normal file
1
tests/components/sha256/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common.yaml
|
Reference in New Issue
Block a user