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
11 changes: 11 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
BasedOnStyle: LLVM
ColumnLimit: 100
BinPackArguments: false
BinPackParameters: false
AllowAllArgumentsOnNextLine: false
AlignAfterOpenBracket: BlockIndent
UseTab: ForIndentation
IndentWidth: 4
TabWidth: 4
ContinuationIndentWidth: 4
AllowShortFunctionsOnASingleLine: None
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8

[*.{c,cc,cpp,h,hpp,ino}]
indent_style = tab
indent_size = tab
tab_width = 4
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
.venv
build/
build_prev_runner/
.vscode
19 changes: 19 additions & 0 deletions .vscode/bin/clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash

set -euo pipefail

if command -v clang-format >/dev/null 2>&1; then
exec clang-format "$@"
fi

_home_dir="${HOME:-}"
if [ -n "$_home_dir" ]; then
_candidate="$(ls -1d "$_home_dir"/.vscode/extensions/ms-vscode.cpptools-*-linux-x64/LLVM/bin/clang-format 2>/dev/null | tail -n 1 || true)"
if [ -n "$_candidate" ] && [ -x "$_candidate" ]; then
exec "$_candidate" "$@"
fi
fi

echo "clang-format executable not found." >&2
echo "Install clang-format system-wide or install/update ms-vscode.cpptools." >&2
exit 127
9 changes: 9 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"recommendations": [
"pioarduino.pioarduino-ide",
"xaver.clang-format"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}
30 changes: 30 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"files.associations": {
"*.ino": "cpp"
},
"editor.defaultFormatter": "xaver.clang-format",
"C_Cpp.formatting": "Disabled",
"clang-format.style": "file",
"clang-format.executable": "${workspaceRoot}/.vscode/bin/clang-format",
"[cpp]": {
"editor.defaultFormatter": "xaver.clang-format",
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"editor.formatOnSave": true
},
"[c]": {
"editor.defaultFormatter": "xaver.clang-format",
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"editor.formatOnSave": true
},
"[arduino]": {
"editor.defaultFormatter": "xaver.clang-format",
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"editor.formatOnSave": true
}
}
12 changes: 12 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Format Firmware Sources",
"type": "shell",
"command": "bash ${workspaceFolder}/scripts/format_cpp.sh",
"group": "build",
"problemMatcher": []
}
]
}
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ void rotate_keys() {
## Tests
Hardware exercises run via PlatformIO Unity tests under `test/test_esp_crypto`, including KATs for SHA-2, AES-GCM (with tag checks), HKDF, PBKDF2, JWT HS256 round-trips, and password hashing. Host-side CMake just stubs out tests (ESP-IDF primitives are unavailable when cross-compiling for CI).

## Formatting Baseline

This repository follows the firmware formatting baseline from `esptoolkit-template`:
- `.clang-format` is the source of truth for C/C++/INO layout.
- `.editorconfig` enforces tabs (`tab_width = 4`), LF endings, and final newline.
- Format all tracked firmware sources with `bash scripts/format_cpp.sh`.

## License
MIT — see [LICENSE.md](LICENSE.md).

Expand Down
226 changes: 148 additions & 78 deletions examples/advanced_primitives/advanced_primitives.ino
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include <Arduino.h>
#include <ESPCrypto.h>

#include <vector>
#include <string>
#include <vector>

const char *RSA_PRIVATE_PEM = R"(-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwudwslbzHhgGu
Expand Down Expand Up @@ -55,93 +55,163 @@ IuWpNdrP2f4GvtZYKkeYrhXDidn1+qYo+jGWUwmCdbo0yKmpDYwmy3/BnQ==
-----END PUBLIC KEY-----)";

String bytesToHex(const std::vector<uint8_t> &bytes) {
static const char *HEX_DIGITS = "0123456789ABCDEF";
String out;
for (uint8_t b : bytes) {
out += HEX_DIGITS[(b >> 4) & 0x0F];
out += HEX_DIGITS[b & 0x0F];
}
return out;
static const char *HEX_DIGITS = "0123456789ABCDEF";
String out;
for (uint8_t b : bytes) {
out += HEX_DIGITS[(b >> 4) & 0x0F];
out += HEX_DIGITS[b & 0x0F];
}
return out;
}

String statusText(const CryptoStatusDetail &status) {
if (status.ok()) {
return "ok";
}
if (status.message.length() > 0) {
return status.message;
}
return String(toString(status.code));
if (status.ok()) {
return "ok";
}
if (status.message.length() > 0) {
return status.message;
}
return String(toString(status.code));
}

void logCaps() {
CryptoCaps caps = ESPCrypto::caps();
Serial.printf("HW accel → SHA:%s AES:%s GCM:%s\n",
caps.shaAccel ? "yes" : "no",
caps.aesAccel ? "yes" : "no",
caps.aesGcmAccel ? "yes" : "no");
CryptoCaps caps = ESPCrypto::caps();
Serial.printf(
"HW accel → SHA:%s AES:%s GCM:%s\n",
caps.shaAccel ? "yes" : "no",
caps.aesAccel ? "yes" : "no",
caps.aesGcmAccel ? "yes" : "no"
);
}

void setup() {
Serial.begin(115200);
delay(200);

// Tighten policy (PBKDF2 iterations >= 2048 by default here)
CryptoPolicy pol = ESPCrypto::policy();
pol.minPbkdf2Iterations = 2048;
ESPCrypto::setPolicy(pol);

logCaps();

// Secure key material that zeroizes on scope exit
SecureBuffer key(32);
for (size_t i = 0; i < key.size(); ++i) {
key.raw()[i] = static_cast<uint8_t>(0xA0 + i);
}

// HMAC-SHA256
std::vector<uint8_t> msg = {'a', 'p', 'i'};
auto hmac = ESPCrypto::hmac(ShaVariant::SHA256, CryptoSpan<const uint8_t>(key.raw()), CryptoSpan<const uint8_t>(msg));
Serial.printf("HMAC-SHA256: %s (status=%s)\n", bytesToHex(hmac.value).c_str(), statusText(hmac.status).c_str());

// HKDF derive two subkeys
std::vector<uint8_t> salt = {0x01, 0x02, 0x03, 0x04};
std::vector<uint8_t> info = {'h', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'};
auto hkdf = ESPCrypto::hkdf(ShaVariant::SHA256, CryptoSpan<const uint8_t>(salt), CryptoSpan<const uint8_t>(key.raw()), CryptoSpan<const uint8_t>(info), 32);
Serial.printf("HKDF key: %s (status=%s)\n", bytesToHex(hkdf.value).c_str(), statusText(hkdf.status).c_str());

// PBKDF2 (policy-enforced iterations)
std::vector<uint8_t> passwordSalt = {0x10, 0x20, 0x30, 0x40, 0x50};
auto pbkdf2 = ESPCrypto::pbkdf2("wifi-password", CryptoSpan<const uint8_t>(passwordSalt), pol.minPbkdf2Iterations, 32);
Serial.printf("PBKDF2: %s (status=%s)\n", bytesToHex(pbkdf2.value).c_str(), statusText(pbkdf2.status).c_str());

// AES-CTR streaming demo
std::vector<uint8_t> ctrNonce(16, 0x00);
for (size_t i = 0; i < ctrNonce.size(); ++i) {
ctrNonce[i] = static_cast<uint8_t>(i);
}
std::vector<uint8_t> streamInput = {'s', 't', 'r', 'e', 'a', 'm', '-', 'c', 't', 'r'};
auto ctrOut = ESPCrypto::aesCtrCrypt(key.raw(), ctrNonce, streamInput);
Serial.printf("AES-CTR cipher: %s (status=%s)\n", bytesToHex(ctrOut.value).c_str(), statusText(ctrOut.status).c_str());
auto ctrPlain = ESPCrypto::aesCtrCrypt(key.raw(), ctrNonce, ctrOut.value);
Serial.printf("AES-CTR plain: %s (status=%s)\n",
String(reinterpret_cast<const char *>(ctrPlain.value.data()), ctrPlain.value.size()).c_str(),
statusText(ctrPlain.status).c_str());

// RSA sign/verify
std::vector<uint8_t> firmware = {'f', 'w', '-', '1', '.', '0'};
auto rsaSig = ESPCrypto::rsaSign(std::string(RSA_PRIVATE_PEM), CryptoSpan<const uint8_t>(firmware), ShaVariant::SHA256);
Serial.printf("RSA sig bytes: %u (status=%s)\n", static_cast<unsigned>(rsaSig.value.size()), statusText(rsaSig.status).c_str());
auto rsaVerify = ESPCrypto::rsaVerify(std::string(RSA_PUBLIC_PEM), CryptoSpan<const uint8_t>(firmware), CryptoSpan<const uint8_t>(rsaSig.value), ShaVariant::SHA256);
Serial.printf("RSA verify: %s (status=%s)\n", rsaVerify.ok() ? "ok" : "fail", statusText(rsaVerify.status).c_str());

// ECDSA sign/verify
auto eccSig = ESPCrypto::eccSign(std::string(ECC_PRIVATE_PEM), CryptoSpan<const uint8_t>(firmware), ShaVariant::SHA256);
Serial.printf("ECC sig bytes: %u (status=%s)\n", static_cast<unsigned>(eccSig.value.size()), statusText(eccSig.status).c_str());
auto eccVerify = ESPCrypto::eccVerify(std::string(ECC_PUBLIC_PEM), CryptoSpan<const uint8_t>(firmware), CryptoSpan<const uint8_t>(eccSig.value), ShaVariant::SHA256);
Serial.printf("ECC verify: %s (status=%s)\n", eccVerify.ok() ? "ok" : "fail", statusText(eccVerify.status).c_str());
Serial.begin(115200);
delay(200);

// Tighten policy (PBKDF2 iterations >= 2048 by default here)
CryptoPolicy pol = ESPCrypto::policy();
pol.minPbkdf2Iterations = 2048;
ESPCrypto::setPolicy(pol);

logCaps();

// Secure key material that zeroizes on scope exit
SecureBuffer key(32);
for (size_t i = 0; i < key.size(); ++i) {
key.raw()[i] = static_cast<uint8_t>(0xA0 + i);
}

// HMAC-SHA256
std::vector<uint8_t> msg = {'a', 'p', 'i'};
auto hmac = ESPCrypto::hmac(
ShaVariant::SHA256,
CryptoSpan<const uint8_t>(key.raw()),
CryptoSpan<const uint8_t>(msg)
);
Serial.printf(
"HMAC-SHA256: %s (status=%s)\n",
bytesToHex(hmac.value).c_str(),
statusText(hmac.status).c_str()
);

// HKDF derive two subkeys
std::vector<uint8_t> salt = {0x01, 0x02, 0x03, 0x04};
std::vector<uint8_t> info = {'h', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'};
auto hkdf = ESPCrypto::hkdf(
ShaVariant::SHA256,
CryptoSpan<const uint8_t>(salt),
CryptoSpan<const uint8_t>(key.raw()),
CryptoSpan<const uint8_t>(info),
32
);
Serial.printf(
"HKDF key: %s (status=%s)\n",
bytesToHex(hkdf.value).c_str(),
statusText(hkdf.status).c_str()
);

// PBKDF2 (policy-enforced iterations)
std::vector<uint8_t> passwordSalt = {0x10, 0x20, 0x30, 0x40, 0x50};
auto pbkdf2 = ESPCrypto::pbkdf2(
"wifi-password",
CryptoSpan<const uint8_t>(passwordSalt),
pol.minPbkdf2Iterations,
32
);
Serial.printf(
"PBKDF2: %s (status=%s)\n",
bytesToHex(pbkdf2.value).c_str(),
statusText(pbkdf2.status).c_str()
);

// AES-CTR streaming demo
std::vector<uint8_t> ctrNonce(16, 0x00);
for (size_t i = 0; i < ctrNonce.size(); ++i) {
ctrNonce[i] = static_cast<uint8_t>(i);
}
std::vector<uint8_t> streamInput = {'s', 't', 'r', 'e', 'a', 'm', '-', 'c', 't', 'r'};
auto ctrOut = ESPCrypto::aesCtrCrypt(key.raw(), ctrNonce, streamInput);
Serial.printf(
"AES-CTR cipher: %s (status=%s)\n",
bytesToHex(ctrOut.value).c_str(),
statusText(ctrOut.status).c_str()
);
auto ctrPlain = ESPCrypto::aesCtrCrypt(key.raw(), ctrNonce, ctrOut.value);
Serial.printf(
"AES-CTR plain: %s (status=%s)\n",
String(reinterpret_cast<const char *>(ctrPlain.value.data()), ctrPlain.value.size())
.c_str(),
statusText(ctrPlain.status).c_str()
);

// RSA sign/verify
std::vector<uint8_t> firmware = {'f', 'w', '-', '1', '.', '0'};
auto rsaSig = ESPCrypto::rsaSign(
std::string(RSA_PRIVATE_PEM),
CryptoSpan<const uint8_t>(firmware),
ShaVariant::SHA256
);
Serial.printf(
"RSA sig bytes: %u (status=%s)\n",
static_cast<unsigned>(rsaSig.value.size()),
statusText(rsaSig.status).c_str()
);
auto rsaVerify = ESPCrypto::rsaVerify(
std::string(RSA_PUBLIC_PEM),
CryptoSpan<const uint8_t>(firmware),
CryptoSpan<const uint8_t>(rsaSig.value),
ShaVariant::SHA256
);
Serial.printf(
"RSA verify: %s (status=%s)\n",
rsaVerify.ok() ? "ok" : "fail",
statusText(rsaVerify.status).c_str()
);

// ECDSA sign/verify
auto eccSig = ESPCrypto::eccSign(
std::string(ECC_PRIVATE_PEM),
CryptoSpan<const uint8_t>(firmware),
ShaVariant::SHA256
);
Serial.printf(
"ECC sig bytes: %u (status=%s)\n",
static_cast<unsigned>(eccSig.value.size()),
statusText(eccSig.status).c_str()
);
auto eccVerify = ESPCrypto::eccVerify(
std::string(ECC_PUBLIC_PEM),
CryptoSpan<const uint8_t>(firmware),
CryptoSpan<const uint8_t>(eccSig.value),
ShaVariant::SHA256
);
Serial.printf(
"ECC verify: %s (status=%s)\n",
eccVerify.ok() ? "ok" : "fail",
statusText(eccVerify.status).c_str()
);
}

void loop() {
vTaskDelay(pdMS_TO_TICKS(1000));
vTaskDelay(pdMS_TO_TICKS(1000));
}
Loading