CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
GpgRecvStore.cpp
Go to the documentation of this file.
2#include "cdc_core/Raii.h"
3#include "cdc_log.h"
4#include "nvs.h"
5#include "nvs_flash.h"
6#include <algorithm>
7#include <cstdio>
8#include <cstring>
9
10static const char* TAG = "GPG_RECV";
11
12namespace cdc::mod_gpg {
13
14namespace {
15constexpr const char* kNamespace = "gpg_recv";
16constexpr const char* kKeyPrefix = "pk_";
17constexpr size_t kNvsKeyLen = 11; // "pk_" + 8 hex chars
18
19bool hasKeyPrefix(const char* k) {
20 return k && std::strncmp(k, kKeyPrefix, 3) == 0;
21}
22} // namespace
23
24GpgRecvStore& GpgRecvStore::instance() {
25 static GpgRecvStore inst;
26 return inst;
27}
28
29void GpgRecvStore::deriveKeyName(const uint8_t fp_v4[20], char out[16]) {
30 std::snprintf(out, 16, "%s%02x%02x%02x%02x",
31 kKeyPrefix, fp_v4[0], fp_v4[1], fp_v4[2], fp_v4[3]);
32}
33
34bool GpgRecvStore::readByName(const char* nvs_key, gpg_recv_key_t* out) {
35 if (!nvs_key || !out) return false;
36 ::cdc::core::NvsScope nvs(kNamespace, NVS_READONLY);
37 if (!nvs) return false;
38 size_t expected = sizeof(gpg_recv_key_t);
39 if (nvs_get_blob(nvs, nvs_key, out, &expected) != ESP_OK) return false;
40 return expected == sizeof(gpg_recv_key_t);
41}
42
43bool GpgRecvStore::writeByName(const char* nvs_key, const gpg_recv_key_t& key) {
44 if (!nvs_key) return false;
45 ::cdc::core::NvsScope nvs(kNamespace, NVS_READWRITE);
46 if (!nvs) return false;
47 if (nvs_set_blob(nvs, nvs_key, &key, sizeof(key)) != ESP_OK) return false;
48 return nvs.commit() == ESP_OK;
49}
50
52 char name[16];
53 deriveKeyName(key.fingerprint_v4, name);
54
55 // Refuse new inserts past the cap, but always allow overwriting
56 // an entry that already exists with the same fingerprint.
57 gpg_recv_key_t probe;
58 bool exists = readByName(name, &probe);
59 if (!exists && count() >= kMaxKeys) {
60 LOG_W(TAG, "Store full (%u keys)", static_cast<unsigned>(kMaxKeys));
61 return false;
62 }
63 return writeByName(name, key);
64}
65
67 nvs_iterator_t it = nullptr;
68 if (nvs_entry_find("nvs", kNamespace, NVS_TYPE_BLOB, &it) != ESP_OK || !it) {
69 return 0;
70 }
71 uint8_t n = 0;
72 while (it != nullptr) {
73 nvs_entry_info_t info = {};
74 nvs_entry_info(it, &info);
75 if (hasKeyPrefix(info.key)) ++n;
76 if (n >= kMaxKeys) break;
77 if (nvs_entry_next(&it) != ESP_OK) break;
78 }
79 nvs_release_iterator(it);
80 return n;
81}
82
84 if (!out || max == 0) return 0;
85
86 nvs_iterator_t it = nullptr;
87 if (nvs_entry_find("nvs", kNamespace, NVS_TYPE_BLOB, &it) != ESP_OK || !it) {
88 return 0;
89 }
90
91 uint8_t n = 0;
92 while (it != nullptr && n < max) {
93 nvs_entry_info_t info = {};
94 nvs_entry_info(it, &info);
95 if (hasKeyPrefix(info.key)) {
97 if (readByName(info.key, &k)) {
98 std::snprintf(out[n].nvs_key, sizeof(out[n].nvs_key), "%s", info.key);
99 out[n].received_at = k.received_at;
100 out[n].flags = k.flags;
101 ++n;
102 }
103 }
104 if (nvs_entry_next(&it) != ESP_OK) break;
105 }
106 nvs_release_iterator(it);
107
108 std::sort(out, out + n,
109 [](const gpg_recv_index_entry_t& a, const gpg_recv_index_entry_t& b) {
110 return a.received_at < b.received_at;
111 });
112 return n;
113}
114
115bool GpgRecvStore::resolveKeyName(uint8_t index, char out[16]) {
117 if (!buf) return false;
118 uint8_t n = listIndex(buf.get(), kMaxKeys);
119 if (index >= n) return false;
120 std::snprintf(out, 16, "%s", buf[index].nvs_key);
121 return true;
122}
123
124bool GpgRecvStore::getKey(uint8_t index, gpg_recv_key_t* out) {
125 if (!out) return false;
126 char name[16];
127 if (!resolveKeyName(index, name)) return false;
128 return readByName(name, out);
129}
130
131bool GpgRecvStore::deleteKey(uint8_t index) {
132 char name[16];
133 if (!resolveKeyName(index, name)) return false;
134
135 ::cdc::core::NvsScope nvs(kNamespace, NVS_READWRITE);
136 if (!nvs) return false;
137 if (nvs_erase_key(nvs, name) != ESP_OK) return false;
138 return nvs.commit() == ESP_OK;
139}
140
141bool GpgRecvStore::setSignature(uint8_t index,
142 const uint8_t* sig, uint8_t sig_len,
143 uint8_t flags)
144{
145 if (!sig || sig_len == 0 || sig_len > sizeof(gpg_recv_key_t::my_signature)) {
146 return false;
147 }
148
149 char name[16];
150 if (!resolveKeyName(index, name)) return false;
151
152 gpg_recv_key_t key;
153 if (!readByName(name, &key)) return false;
154
155 std::memset(key.my_signature, 0, sizeof(key.my_signature));
156 std::memcpy(key.my_signature, sig, sig_len);
157 key.sig_len = sig_len;
158 key.flags = flags;
159
160 return writeByName(name, key);
161}
162
163} // namespace cdc::mod_gpg
static const char * TAG
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
uint8_t flags
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
RAII wrapper for an NVS handle.
Definition Raii.h:106
esp_err_t commit() noexcept
Commit pending writes. Caller checks the return value if needed.
Definition Raii.h:153
bool addKey(const gpg_recv_key_t &key)
Persist a new key. Replaces an existing entry if the fingerprint matches.
bool deleteKey(uint8_t index)
Remove one key by sorted index. No-op if index is out of range.
bool setSignature(uint8_t index, const uint8_t *sig, uint8_t sig_len, uint8_t flags)
Attach a cross-signature and flag bits to an existing entry.
bool getKey(uint8_t index, gpg_recv_key_t *out)
Load one key by sorted index (0..count()-1).
static GpgRecvStore & instance()
uint8_t listIndex(gpg_recv_index_entry_t *out, uint8_t max)
Build the sorted index (oldest first).
uint8_t count()
Number of stored keys.
static constexpr uint8_t kMaxKeys
Hard ceiling. Past this addKey rejects further inserts.
PsramUniquePtr< T > psramAlloc(std::size_t count) noexcept
Allocate count elements of T in PSRAM (8-bit capable region).
Definition Raii.h:51
Sort entry used to expose a stable ordered index over NVS keys.
uint8_t flags
uint32_t received_at
One GPG public key received from another badge.