CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
GpgStorage.cpp
Go to the documentation of this file.
1
8
11#include "cdc_core/Crypto.h"
12#include "cdc_log.h"
13#include <mbedtls/sha256.h>
14#include <mbedtls/hkdf.h>
15#include <mbedtls/md.h>
16#include <mbedtls/platform_util.h>
17#include <esp_random.h>
18#include <string.h>
19
20static const char* TAG = "GPGStorage";
21
22// Relative offsets within the mod_gpg R-Memory range. Each offset is the
23// metadata slot for the matching ECC key slot (RMEM slot N pairs with ECC
24// slot N). The range starts at slot 1 (= ECC slot 1 = SIG), which is
25// hardware-only and needs no software metadata, so offset 0 is unused.
27static constexpr uint16_t RMEM_SLOT_DEC_KEY = 1;
28
30static constexpr uint16_t RMEM_SLOT_AES_KEY = 2;
31
33static constexpr uint8_t DEC_KEY_MAGIC[4] = {'E', 'C', 'D', 'H'};
34
36static constexpr uint8_t AES_KEY_MAGIC[4] = {'A', 'E', 'S', '1'};
37
38static constexpr size_t MAGIC_SIZE = 4;
39static constexpr size_t NONCE_SIZE = 12;
40static constexpr size_t TAG_SIZE = 16;
41static constexpr size_t PRIVKEY_SIZE = 32;
42static constexpr size_t AES_MAX_KEY_SIZE = 32;
44static constexpr size_t AES_RECORD_PAYLOAD = 1 + AES_MAX_KEY_SIZE;
46
48static constexpr char HKDF_INFO[] = "GPG-STORAGE-V2";
49
50#ifdef __DOXYGEN__
51namespace cdc::mod_gpg {
52#endif
53
54#pragma pack(push, 1)
56 uint8_t magic[MAGIC_SIZE];
57 uint8_t nonce[NONCE_SIZE];
59 uint8_t tag[TAG_SIZE];
60};
61
68#pragma pack(pop)
69
70#ifdef __DOXYGEN__
71} // namespace cdc::mod_gpg
72#endif
73
74static_assert(sizeof(DecKeyStorage) == DEC_TOTAL_SIZE, "DecKeyStorage size mismatch");
75static_assert(sizeof(AesKeyStorage) == AES_TOTAL_SIZE, "AesKeyStorage size mismatch");
76
77namespace {
78
79template <size_t N>
80inline void secureWipe(uint8_t (&buf)[N]) {
81 mbedtls_platform_zeroize(buf, N);
82}
83
84template <typename T>
85inline void secureWipeObject(T& obj) {
86 mbedtls_platform_zeroize(&obj, sizeof(obj));
87}
88
89} // namespace
90
91static struct {
92 bool ready = false;
93 uint16_t eccStart = 0;
94 uint16_t eccEnd = 0;
95 uint16_t rmemStart = 0;
96 uint16_t rmemEnd = 0;
97 uint8_t sigSlot = 0;
98 uint8_t decSlot = 0;
99 uint8_t autSlot = 0;
100
101 bool sessionActive = false;
102 uint8_t sessionKey[32];
104
108
115static bool pin_to_hash(const char* pin, uint8_t* hash_out) {
116 if (!hash_out) return false;
117 const uint8_t* in = pin ? reinterpret_cast<const uint8_t*>(pin) : reinterpret_cast<const uint8_t*>("");
118 size_t in_len = pin ? strlen(pin) : 0;
119 return mbedtls_sha256(in, in_len, hash_out, 0) == 0;
120}
121
129static bool derive_storage_key(uint16_t slot_id, const uint8_t* pin_hash, uint8_t* key_out) {
130 if (!key_out) return false;
131
132 uint8_t ikm[16 + 32] = {};
133 size_t ikm_len = 16;
134 auto* se = get_se();
135 if (se) {
136 se->getChipId(ikm, 16);
137 }
138 if (pin_hash) {
139 memcpy(ikm + 16, pin_hash, 32);
140 ikm_len = 16 + 32;
141 }
142
143 uint8_t salt[2];
144 salt[0] = static_cast<uint8_t>((slot_id >> 8) & 0xFF);
145 salt[1] = static_cast<uint8_t>(slot_id & 0xFF);
146
147 const mbedtls_md_info_t* md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
148 if (!md) {
149 mbedtls_platform_zeroize(ikm, sizeof(ikm));
150 return false;
151 }
152
153 int ret = mbedtls_hkdf(
154 md,
155 salt, sizeof(salt),
156 ikm, ikm_len,
157 reinterpret_cast<const uint8_t*>(HKDF_INFO), strlen(HKDF_INFO),
158 key_out, 32
159 );
160
161 mbedtls_platform_zeroize(ikm, sizeof(ikm));
162 return ret == 0;
163}
164
171static void build_aad(uint16_t slot_id, const uint8_t magic[MAGIC_SIZE], uint8_t aad_out[6]) {
172 aad_out[0] = static_cast<uint8_t>((slot_id >> 8) & 0xFF);
173 aad_out[1] = static_cast<uint8_t>(slot_id & 0xFF);
174 memcpy(aad_out + 2, magic, MAGIC_SIZE);
175}
176
182static uint16_t resolve_slot(uint16_t rel_index) {
183 return static_cast<uint16_t>(s_storage.rmemStart + rel_index);
184}
185
186void gpg_storage_set_slot_range(uint16_t eccStart, uint16_t eccEnd) {
187 s_storage.ready = false;
188 s_storage.eccStart = eccStart;
189 s_storage.eccEnd = eccEnd;
190
191 if (eccStart == 0 || eccEnd == 0 || eccStart > eccEnd || (eccEnd - eccStart + 1) < 3) {
192 return;
193 }
194
195 s_storage.sigSlot = static_cast<uint8_t>(eccStart);
196 s_storage.decSlot = static_cast<uint8_t>(eccStart + 1);
197 s_storage.autSlot = static_cast<uint8_t>(eccStart + 2);
198 s_storage.ready = true;
199}
200
201void gpg_storage_set_rmem_range(uint16_t rmemStart, uint16_t rmemEnd) {
202 s_storage.rmemStart = rmemStart;
203 s_storage.rmemEnd = rmemEnd;
204}
205
206bool gpg_storage_ready(void) { return s_storage.ready; }
207uint8_t gpg_storage_sig_slot(void) { return s_storage.sigSlot; }
208uint8_t gpg_storage_dec_slot(void) { return s_storage.decSlot; }
209uint8_t gpg_storage_aut_slot(void) { return s_storage.autSlot; }
210
222static bool save_slot_encrypted(uint16_t slot_id,
223 const uint8_t magic[MAGIC_SIZE],
224 const uint8_t* payload, size_t payload_len,
225 uint8_t* record_buf, size_t record_buf_len,
226 const char* pin) {
227 auto* se = get_se();
228 if (!se) return false;
229 if (record_buf_len < MAGIC_SIZE + NONCE_SIZE + payload_len + TAG_SIZE) return false;
230
231 uint8_t pin_hash[32];
232 bool have_pin = (pin && pin[0] != '\0');
233 if (have_pin && !pin_to_hash(pin, pin_hash)) {
234 mbedtls_platform_zeroize(pin_hash, sizeof(pin_hash));
235 return false;
236 }
237
238 uint8_t enc_key[32];
239 bool ok = derive_storage_key(slot_id, have_pin ? pin_hash : nullptr, enc_key);
240 mbedtls_platform_zeroize(pin_hash, sizeof(pin_hash));
241 if (!ok) {
242 secureWipe(enc_key);
243 return false;
244 }
245
246 uint8_t* p_magic = record_buf;
247 uint8_t* p_nonce = record_buf + MAGIC_SIZE;
248 uint8_t* p_ct = record_buf + MAGIC_SIZE + NONCE_SIZE;
249 uint8_t* p_tag = p_ct + payload_len;
250
251 memcpy(p_magic, magic, MAGIC_SIZE);
252 if (!se->getRandomStrict(p_nonce, NONCE_SIZE)) {
253 secureWipe(enc_key);
254 LOG_E(TAG, "Cannot get hardware entropy for nonce on slot %u", slot_id);
255 return false;
256 }
257
258 uint8_t aad[6];
259 build_aad(slot_id, magic, aad);
260
261 bool enc_ok = cdc::core::aesGcm256Seal(
262 enc_key, p_nonce, NONCE_SIZE,
263 aad, sizeof(aad),
264 payload, payload_len,
265 p_ct, p_tag);
266 secureWipe(enc_key);
267 if (!enc_ok) {
268 LOG_E(TAG, "GCM encrypt failed");
269 return false;
270 }
271
272 se->rmemErase(slot_id);
273 size_t total_len = MAGIC_SIZE + NONCE_SIZE + payload_len + TAG_SIZE;
274 if (se->rmemWrite(slot_id, record_buf, static_cast<uint16_t>(total_len)) != cdc::hal::SeResult::OK) {
275 LOG_E(TAG, "rmemWrite slot %u failed", slot_id);
276 return false;
277 }
278 return true;
279}
280
290static bool load_slot_decrypted(uint16_t slot_id,
291 const uint8_t magic[MAGIC_SIZE],
292 uint8_t* payload_out, size_t payload_len,
293 const char* pin) {
294 auto* se = get_se();
295 if (!se) return false;
296
297 uint8_t buf[128];
298 uint16_t buf_len = 0;
299 size_t expected = MAGIC_SIZE + NONCE_SIZE + payload_len + TAG_SIZE;
300 if (expected > sizeof(buf)) return false;
301
302 if (se->rmemRead(slot_id, buf, sizeof(buf), &buf_len) != cdc::hal::SeResult::OK || buf_len < expected) {
303 return false;
304 }
305 if (memcmp(buf, magic, MAGIC_SIZE) != 0) {
306 return false;
307 }
308
309 uint8_t pin_hash[32];
310 bool have_pin = (pin && pin[0] != '\0');
311 if (have_pin && !pin_to_hash(pin, pin_hash)) {
312 mbedtls_platform_zeroize(pin_hash, sizeof(pin_hash));
313 mbedtls_platform_zeroize(buf, sizeof(buf));
314 return false;
315 }
316
317 uint8_t dec_key[32];
318 bool ok = derive_storage_key(slot_id, have_pin ? pin_hash : nullptr, dec_key);
319 mbedtls_platform_zeroize(pin_hash, sizeof(pin_hash));
320 if (!ok) {
321 secureWipe(dec_key);
322 mbedtls_platform_zeroize(buf, sizeof(buf));
323 return false;
324 }
325
326 const uint8_t* p_nonce = buf + MAGIC_SIZE;
327 const uint8_t* p_ct = buf + MAGIC_SIZE + NONCE_SIZE;
328 const uint8_t* p_tag = p_ct + payload_len;
329
330 uint8_t aad[6];
331 build_aad(slot_id, magic, aad);
332
333 bool dec_ok = cdc::core::aesGcm256Open(
334 dec_key, p_nonce, NONCE_SIZE,
335 aad, sizeof(aad),
336 p_ct, payload_len,
337 p_tag, payload_out);
338 secureWipe(dec_key);
339 mbedtls_platform_zeroize(buf, sizeof(buf));
340 if (!dec_ok) {
341 mbedtls_platform_zeroize(payload_out, payload_len);
342 return false;
343 }
344 return true;
345}
346
347bool gpg_storage_save_dec_privkey(const uint8_t* privkey, const char* pin) {
348 if (!privkey) return false;
349 uint16_t slot = resolve_slot(RMEM_SLOT_DEC_KEY);
350 uint8_t record[DEC_TOTAL_SIZE];
351 bool ok = save_slot_encrypted(slot, DEC_KEY_MAGIC, privkey, PRIVKEY_SIZE,
352 record, sizeof(record), pin);
353 secureWipe(record);
354 if (ok) {
355 LOG_I(TAG, "Saved DEC private key (slot %u)", slot);
356 }
357 return ok;
358}
359
360bool gpg_storage_load_dec_privkey(uint8_t* privkey_out, const char* pin) {
361 if (!privkey_out) return false;
362 uint16_t slot = resolve_slot(RMEM_SLOT_DEC_KEY);
363 return load_slot_decrypted(slot, DEC_KEY_MAGIC, privkey_out, PRIVKEY_SIZE, pin);
364}
365
367 auto* se = get_se();
368 if (!se) return false;
369 uint16_t slot = resolve_slot(RMEM_SLOT_DEC_KEY);
370 uint8_t buf[DEC_TOTAL_SIZE];
371 uint16_t buf_len = 0;
372 if (se->rmemRead(slot, buf, sizeof(buf), &buf_len) != cdc::hal::SeResult::OK || buf_len < MAGIC_SIZE) {
373 return false;
374 }
375 return memcmp(buf, DEC_KEY_MAGIC, MAGIC_SIZE) == 0;
376}
377
379 auto* se = get_se();
380 if (!se) return false;
381 uint16_t slot = resolve_slot(RMEM_SLOT_DEC_KEY);
382 return se->rmemErase(slot) == cdc::hal::SeResult::OK;
383}
384
385bool gpg_storage_save_aes_key(const uint8_t* key, size_t key_len, const char* pin) {
386 if (!key) return false;
387 if (key_len != 16 && key_len != 32) return false;
388 uint16_t slot = resolve_slot(RMEM_SLOT_AES_KEY);
389
390 uint8_t payload[AES_RECORD_PAYLOAD];
391 payload[0] = static_cast<uint8_t>(key_len);
392 memcpy(payload + 1, key, key_len);
393 if (key_len < AES_MAX_KEY_SIZE) {
394 memset(payload + 1 + key_len, 0, AES_MAX_KEY_SIZE - key_len);
395 }
396
397 uint8_t record[AES_TOTAL_SIZE];
398 bool ok = save_slot_encrypted(slot, AES_KEY_MAGIC, payload, sizeof(payload),
399 record, sizeof(record), pin);
400 secureWipe(payload);
401 secureWipe(record);
402 if (ok) {
403 LOG_I(TAG, "Saved AES key (slot %u, %zu bytes)", slot, key_len);
404 }
405 return ok;
406}
407
408bool gpg_storage_load_aes_key(uint8_t* key_out, size_t* key_len_out, const char* pin) {
409 if (!key_out || !key_len_out) return false;
410 uint16_t slot = resolve_slot(RMEM_SLOT_AES_KEY);
411
412 uint8_t payload[AES_RECORD_PAYLOAD];
413 if (!load_slot_decrypted(slot, AES_KEY_MAGIC, payload, sizeof(payload), pin)) {
414 return false;
415 }
416 size_t len = payload[0];
417 if (len != 16 && len != 32) {
418 secureWipe(payload);
419 return false;
420 }
421 memcpy(key_out, payload + 1, len);
422 *key_len_out = len;
423 secureWipe(payload);
424 return true;
425}
426
428 auto* se = get_se();
429 if (!se) return false;
430 uint16_t slot = resolve_slot(RMEM_SLOT_AES_KEY);
431 uint8_t buf[MAGIC_SIZE];
432 uint16_t buf_len = 0;
433 if (se->rmemRead(slot, buf, sizeof(buf), &buf_len) != cdc::hal::SeResult::OK || buf_len < MAGIC_SIZE) {
434 return false;
435 }
436 return memcmp(buf, AES_KEY_MAGIC, MAGIC_SIZE) == 0;
437}
438
440 auto* se = get_se();
441 if (!se) return false;
442 uint16_t slot = resolve_slot(RMEM_SLOT_AES_KEY);
443 return se->rmemErase(slot) == cdc::hal::SeResult::OK;
444}
445
446void gpg_storage_set_session_pin(const char* pin) {
447 if (!pin) {
449 return;
450 }
451 uint8_t hash[32];
452 if (pin_to_hash(pin, hash)) {
453 memcpy(s_storage.sessionKey, hash, sizeof(hash));
454 s_storage.sessionActive = true;
455 }
456 mbedtls_platform_zeroize(hash, sizeof(hash));
457}
458
459bool gpg_storage_get_session_key(uint8_t* key_out) {
460 if (!s_storage.sessionActive || !key_out) {
461 return false;
462 }
463 memcpy(key_out, s_storage.sessionKey, 32);
464 return true;
465}
466
468 mbedtls_platform_zeroize(s_storage.sessionKey, sizeof(s_storage.sessionKey));
469 s_storage.sessionActive = false;
470}
static const char * TAG
Shared AES-256-GCM helpers built on mbedTLS.
static bool save_slot_encrypted(uint16_t slot_id, const uint8_t magic[MAGIC_SIZE], const uint8_t *payload, size_t payload_len, uint8_t *record_buf, size_t record_buf_len, const char *pin)
Encrypts and writes an arbitrary payload to a slot.
static void build_aad(uint16_t slot_id, const uint8_t magic[MAGIC_SIZE], uint8_t aad_out[6])
Builds the 6-byte AAD for a slot: slot_id (BE) || magic (4).
static bool load_slot_decrypted(uint16_t slot_id, const uint8_t magic[MAGIC_SIZE], uint8_t *payload_out, size_t payload_len, const char *pin)
Reads and decrypts a payload from a slot.
bool gpg_storage_save_aes_key(const uint8_t *key, size_t key_len, const char *pin)
Saves the symmetric AES key for PSO:DECIPHER (DO 0xD5).
static bool pin_to_hash(const char *pin, uint8_t *hash_out)
Computes SHA-256 over a PIN string.
static constexpr size_t DEC_TOTAL_SIZE
void gpg_storage_set_rmem_range(uint16_t rmemStart, uint16_t rmemEnd)
bool gpg_storage_delete_dec_privkey(void)
Deletes DEC private key record.
static cdc::hal::ISecureElement * get_se()
static constexpr size_t NONCE_SIZE
uint8_t gpg_storage_dec_slot(void)
bool gpg_storage_ready(void)
bool gpg_storage_save_dec_privkey(const uint8_t *privkey, const char *pin)
Saves a DEC private key into R-Memory using PIN-bound AES-GCM.
void gpg_storage_set_session_pin(const char *pin)
Stores session PIN-derived key after successful PIN verification.
static constexpr uint16_t RMEM_SLOT_AES_KEY
R-Memory slot offset for the symmetric AES key payload (= ECC slot 3).
bool gpg_storage_load_aes_key(uint8_t *key_out, size_t *key_len_out, const char *pin)
Loads the symmetric AES key from R-Memory.
static bool derive_storage_key(uint16_t slot_id, const uint8_t *pin_hash, uint8_t *key_out)
Derives a 32-byte storage key for a specific slot.
static uint16_t resolve_slot(uint16_t rel_index)
Resolves an absolute R-Memory slot index relative to the module range.
static constexpr uint16_t RMEM_SLOT_DEC_KEY
R-Memory slot offset for the DEC private key payload (= ECC slot 2).
bool gpg_storage_load_dec_privkey(uint8_t *privkey_out, const char *pin)
Loads and decrypts the DEC private key from R-Memory.
uint8_t gpg_storage_aut_slot(void)
static constexpr size_t AES_RECORD_PAYLOAD
void gpg_storage_clear_session(void)
Clears the cached session key.
uint8_t gpg_storage_sig_slot(void)
void gpg_storage_set_slot_range(uint16_t eccStart, uint16_t eccEnd)
static struct @103247112223232226025054031216105275222335301367 s_storage
static constexpr size_t PRIVKEY_SIZE
static constexpr size_t AES_MAX_KEY_SIZE
bool gpg_storage_delete_aes_key(void)
Deletes the symmetric AES key record.
static constexpr size_t TAG_SIZE
bool gpg_storage_has_dec_privkey(void)
Returns true if encrypted DEC private key record exists.
static constexpr size_t MAGIC_SIZE
static constexpr char HKDF_INFO[]
HKDF info string for storage key derivation.
static constexpr uint8_t DEC_KEY_MAGIC[4]
Magic marker for encrypted DEC private key records.
bool gpg_storage_get_session_key(uint8_t *key_out)
Returns current session key if session is active.
bool gpg_storage_has_aes_key(void)
Returns true if a symmetric AES key record exists.
static constexpr size_t AES_TOTAL_SIZE
static constexpr uint8_t AES_KEY_MAGIC[4]
Magic marker for symmetric AES key records (DO 0xD5).
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
bool aesGcm256Seal(const uint8_t key[32], const uint8_t *iv, size_t ivLen, const uint8_t *aad, size_t aadLen, const uint8_t *pt, size_t ptLen, uint8_t *ctOut, uint8_t tagOut[16])
Encrypts pt with AES-256-GCM and produces a 16-byte tag.
Definition Crypto.h:48
bool aesGcm256Open(const uint8_t key[32], const uint8_t *iv, size_t ivLen, const uint8_t *aad, size_t aadLen, const uint8_t *ct, size_t ctLen, const uint8_t tag[16], uint8_t *ptOut)
Authenticates and decrypts ct with AES-256-GCM.
Definition Crypto.h:79
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
uint8_t encrypted[AES_RECORD_PAYLOAD]
uint8_t magic[MAGIC_SIZE]
uint8_t nonce[NONCE_SIZE]
uint8_t magic[MAGIC_SIZE]
uint8_t nonce[NONCE_SIZE]
uint8_t encrypted[PRIVKEY_SIZE]