CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
gpg.cpp
Go to the documentation of this file.
1#include "mod_gpg/gpg.h"
5#include "ecdh.h"
9#include "cdc_log.h"
10#include <mbedtls/sha1.h>
11#include <mbedtls/base64.h>
12#include <mbedtls/platform_util.h>
13#include <cstring>
14#include <ctime>
15
16namespace {
17
18constexpr const char* TAG = "GPG";
19
20// Sizes provided by mod_gpg/openpgp/constants.h:
21// MPI_HEADER_SIZE, ED25519_PUBKEY_SIZE, P256_PUBKEY_SIZE,
22// P256_PUBKEY_BITS, MPI_FULL_SIZE_ED25519, MPI_FULL_SIZE_P256
23
24char s_pending_user_id[GPG_USER_ID_MAX] = {};
25
26uint32_t get_unix_time(void) {
27 return static_cast<uint32_t>(time(nullptr));
28}
29
30// TROPIC01 writes a fixed 64-byte buffer for both Ed25519 (first 32 bytes used)
31// and P-256 (raw X||Y, no leading 0x04 indicator). Callers receive 64 bytes for
32// P-256 and 32 bytes for Ed25519; any 0x04 SEC1 prefix must be added by the
33// consumer.
34bool se_get_pubkey(uint8_t slot, uint8_t* pubkey, size_t max_len, uint8_t* curve_out) {
36 if (!se || !pubkey) return false;
37 if (max_len < 64) {
38 LOG_E(TAG, "se_get_pubkey: buffer too small (slot=%u max_len=%zu)", slot, max_len);
39 return false;
40 }
42 auto res = se->eccGetPublicKey(slot, pubkey, &curve);
43 if (res != cdc::hal::SeResult::OK) {
44 LOG_E(TAG, "se_get_pubkey: slot=%u failed (%d)", slot, static_cast<int>(res));
45 return false;
46 }
47 if (curve_out) {
50 }
51 return true;
52}
53
54bool se_generate_key(uint8_t slot, uint8_t curve) {
56 if (!se) {
57 LOG_E(TAG, "se_generate_key: no SE (slot=%u)", slot);
58 return false;
59 }
61 : cdc::hal::EccCurve::P256;
62 cdc::hal::SeResult res = se->eccGenerate(slot, c);
63 if (res != cdc::hal::SeResult::OK) {
64 LOG_W(TAG, "se_generate_key: slot %u initial fail (%d), retry after delete",
65 slot, static_cast<int>(res));
66 se->eccDelete(slot);
67 res = se->eccGenerate(slot, c);
68 }
69 if (res != cdc::hal::SeResult::OK) {
70 LOG_E(TAG, "se_generate_key: slot %u curve=%u failed (%d)",
71 slot, curve, static_cast<int>(res));
72 return false;
73 }
74 return true;
75}
76
77bool calculate_fingerprint(const uint8_t *pubkey, uint8_t curve,
78 uint32_t created_at, uint8_t *fp_out) {
79 const size_t pubkey_len = (curve == CDC_CURVE_ED25519) ? ED25519_PUBKEY_SIZE : 64;
80 return ::cdc::mod_gpg::calculateFingerprintV4(curve, pubkey, pubkey_len,
81 created_at, fp_out);
82}
83
84} // namespace
85
86bool gpg_init(void) {
87 s_pending_user_id[0] = '\0';
88 return gpg_storage_ready();
89}
90
92 return openpgp_has_any_key();
93}
94
95bool gpg_get_status(gpg_status_t *status) {
96 if (!status) return false;
97 memset(status, 0, sizeof(*status));
98 if (!openpgp_has_any_key()) return false;
99
100 status->initialized = true;
101
102 uint8_t fp[GPG_FINGERPRINT_LEN] = {0};
104 memcpy(status->fingerprint, fp, sizeof(status->fingerprint));
105 }
106 status->created_at = openpgp_get_gen_time(KEY_SIG);
107 status->sign_count = openpgp_get_sig_count();
108 // Curve isn't currently exposed by the OpenPGP card-application state;
109 // default to Ed25519 (the on-device generate default) until that lookup
110 // lands.
111 status->curve = CDC_CURVE_ED25519;
112
113 char name[GPG_USER_ID_MAX] = {0};
115 if (name[0]) {
116 strncpy(status->user_id, name, sizeof(status->user_id) - 1);
117 }
118 return true;
119}
120
122 if (!user_id || !user_id[0]) return false;
123 strncpy(s_pending_user_id, user_id, sizeof(s_pending_user_id) - 1);
124 s_pending_user_id[sizeof(s_pending_user_id) - 1] = '\0';
125 return true;
126}
127
129 return s_pending_user_id[0] != '\0';
130}
131
132bool gpg_generate_key(uint8_t curve) {
133 if (!gpg_storage_ready()) {
134 LOG_E(TAG, "generate: storage not ready");
135 return false;
136 }
138 LOG_E(TAG, "generate: missing user-id");
139 return false;
140 }
141
142 uint8_t sig_slot = gpg_storage_sig_slot();
143 uint8_t dec_slot = gpg_storage_dec_slot();
144 uint8_t aut_slot = gpg_storage_aut_slot();
145 LOG_I(TAG, "generate: curve=%u sig_slot=%u dec_slot=%u aut_slot=%u",
146 curve, sig_slot, dec_slot, aut_slot);
147
148 // SIG and AUT keys live on TROPIC01.
149 if (!se_generate_key(sig_slot, curve)) {
150 LOG_E(TAG, "generate: SIG key generation failed");
151 return false;
152 }
153 if (!se_generate_key(aut_slot, curve)) {
154 LOG_E(TAG, "generate: AUT key generation failed");
155 return false;
156 }
157
158 // DEC key uses software ECDH P-256 (TROPIC01 has no ECDH primitive). The
159 // matching ECC slot stays empty; the encrypted private key lives in
160 // R-Memory via gpg_storage_save_dec_privkey().
162 if (se) {
163 se->eccDelete(dec_slot);
164 }
165 uint8_t dec_priv[P256_PRIVKEY_SIZE] = {};
166 uint8_t dec_pub65[P256_PUBKEY_SIZE] = {};
167 if (!ecdh_p256_generate_keypair(dec_priv, dec_pub65)) {
168 LOG_E(TAG, "generate: DEC ECDH keypair failed");
169 return false;
170 }
171 bool dec_saved = gpg_storage_save_dec_privkey(dec_priv, nullptr);
172 mbedtls_platform_zeroize(dec_priv, sizeof(dec_priv));
173 if (!dec_saved) {
174 LOG_E(TAG, "generate: DEC privkey save failed");
175 return false;
176 }
177
178 uint32_t created_at = get_unix_time();
179
180 uint8_t pub_sig[64] = {};
181 uint8_t pub_aut[64] = {};
182 uint8_t curve_sig = curve;
183 uint8_t curve_aut = curve;
184 if (!se_get_pubkey(sig_slot, pub_sig, sizeof(pub_sig), &curve_sig)) {
185 LOG_E(TAG, "generate: read SIG pubkey failed");
186 return false;
187 }
188 if (!se_get_pubkey(aut_slot, pub_aut, sizeof(pub_aut), &curve_aut)) {
189 LOG_E(TAG, "generate: read AUT pubkey failed");
190 return false;
191 }
192
193 uint8_t fp_sig[GPG_FINGERPRINT_LEN] = {};
194 uint8_t fp_dec[GPG_FINGERPRINT_LEN] = {};
195 uint8_t fp_aut[GPG_FINGERPRINT_LEN] = {};
196 if (!calculate_fingerprint(pub_sig, curve_sig, created_at, fp_sig)) {
197 LOG_E(TAG, "generate: SIG fingerprint failed");
198 return false;
199 }
200 // dec_pub65 layout is 0x04 || X || Y. calculate_fingerprint() expects raw
201 // X || Y for P-256, so skip the leading byte.
202 if (!calculate_fingerprint(dec_pub65 + 1, CDC_CURVE_P256, created_at, fp_dec)) {
203 LOG_E(TAG, "generate: DEC fingerprint failed");
204 return false;
205 }
206 if (!calculate_fingerprint(pub_aut, curve_aut, created_at, fp_aut)) {
207 LOG_E(TAG, "generate: AUT fingerprint failed");
208 return false;
209 }
210
211 openpgp_set_key_fingerprint(KEY_SIG, fp_sig, created_at);
212 openpgp_set_key_fingerprint(KEY_DEC, fp_dec, created_at);
213 openpgp_set_key_fingerprint(KEY_AUT, fp_aut, created_at);
214
215 if (s_pending_user_id[0]) {
216 openpgp_set_cardholder_name(s_pending_user_id);
217 }
218
219 s_pending_user_id[0] = '\0';
220 LOG_I(TAG, "generate: success");
221 return true;
222}
223
224bool gpg_reset(void) {
225 if (!gpg_storage_ready()) return false;
226 // openpgp_factory_reset() wipes ECC slots, DEC privkey, NVS state
227 // (fingerprints, gen-times, counter, cardholder, RC) and PINs.
229 s_pending_user_id[0] = '\0';
230 return true;
231}
232
233bool gpg_export_pubkey_pem(char *buf, size_t size, size_t *out_len) {
234 if (!buf || size < 256 || !out_len) {
235 LOG_W(TAG, "export: bad args");
236 return false;
237 }
239 LOG_W(TAG, "export: no key configured");
240 return false;
241 }
242
244 if (!se) {
245 LOG_W(TAG, "export: no SE");
246 return false;
247 }
248
249 // Buffer sized for the optional 0x04 SEC1 prefix that secure elements
250 // can prepend on P-256 reads (so 65 bytes for P-256 vs 32 for Ed25519).
251 uint8_t pubkey[P256_PUBKEY_SIZE + 1] = {0};
253 uint8_t sig_slot = gpg_storage_sig_slot();
254 auto res = se->eccGetPublicKey(sig_slot, pubkey, &eccCurve);
255 if (res != cdc::hal::SeResult::OK) {
256 LOG_W(TAG, "export: eccGetPublicKey slot=%u failed (%d)",
257 sig_slot, static_cast<int>(res));
258 return false;
259 }
260 uint8_t curve = (eccCurve == cdc::hal::EccCurve::ED25519) ? CDC_CURVE_ED25519
262 if (curve == CDC_CURVE_P256 && pubkey[0] == 0x04) {
263 memmove(pubkey, pubkey + 1, P256_PUBKEY_SIZE);
264 }
265
266 if (curve == CDC_CURVE_ED25519) {
267 static const uint8_t ed25519_prefix[] = {
268 0x30, 0x2a,
269 0x30, 0x05,
270 0x06, 0x03, 0x2b, 0x65, 0x70,
271 0x03, 0x21, 0x00
272 };
273
274 uint8_t der[sizeof(ed25519_prefix) + 32];
275 memcpy(der, ed25519_prefix, sizeof(ed25519_prefix));
276 memcpy(der + sizeof(ed25519_prefix), pubkey, 32);
277
278 size_t b64_len = 0;
279 char b64[128];
280 if (mbedtls_base64_encode(reinterpret_cast<unsigned char*>(b64), sizeof(b64),
281 &b64_len, der, sizeof(der)) != 0) {
282 return false;
283 }
284
285 int written = snprintf(buf, size,
286 "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
287 b64);
288 if (written < 0 || static_cast<size_t>(written) >= size) return false;
289 *out_len = static_cast<size_t>(written);
290 return true;
291 }
292
293 static const uint8_t p256_prefix[] = {
294 0x30, 0x59,
295 0x30, 0x13,
296 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
297 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
298 0x03, 0x42, 0x00
299 };
300 uint8_t der[sizeof(p256_prefix) + 65];
301 memcpy(der, p256_prefix, sizeof(p256_prefix));
302 der[sizeof(p256_prefix)] = 0x04;
303 memcpy(der + sizeof(p256_prefix) + 1, pubkey, 64);
304
305 size_t b64_len = 0;
306 char b64[200];
307 if (mbedtls_base64_encode(reinterpret_cast<unsigned char*>(b64), sizeof(b64),
308 &b64_len, der, sizeof(der)) != 0) {
309 return false;
310 }
311 int written = snprintf(buf, size,
312 "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
313 b64);
314 if (written < 0 || static_cast<size_t>(written) >= size) return false;
315 *out_len = static_cast<size_t>(written);
316 return true;
317}
318
319bool gpg_alchemy_fingerprint(char *buf, size_t len) {
320 if (!buf || len < KEY_FINGERPRINT_MAX_LEN) {
321 return false;
322 }
324}
static const char * TAG
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.
uint8_t gpg_storage_aut_slot(void)
uint8_t gpg_storage_sig_slot(void)
#define KEY_FINGERPRINT_MAX_LEN
bool key_fingerprint_generate(uint8_t slot, char *buf, size_t len)
Reads public key from secure element slot and generates fingerprint.
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
#define ED25519_PUBKEY_SIZE
Ed25519 raw public key size in bytes.
Definition constants.h:56
#define P256_PUBKEY_SIZE
P-256 uncompressed public key size: 0x04 || X(32) || Y(32).
Definition constants.h:42
#define P256_PRIVKEY_SIZE
P-256 private key (scalar) size in bytes.
Definition constants.h:45
bool ecdh_p256_generate_keypair(uint8_t *privkey_out, uint8_t *pubkey_out)
Definition ecdh.cpp:153
#define CDC_CURVE_ED25519
Definition fido2.h:23
#define CDC_CURVE_P256
Definition fido2.h:24
uint8_t curve
uint8_t user_id[FIDO2_USER_ID_MAX_LEN]
bool gpg_has_pending_user_id(void)
Returns whether a user-id was staged via gpg_set_pending_user_id().
Definition gpg.cpp:128
bool gpg_init(void)
Initializes the GPG module bookkeeping.
Definition gpg.cpp:86
bool gpg_set_pending_user_id(const char *user_id)
Stages a user-id string for the next on-device key generation. The string is forwarded to OpenpgpNvsS...
Definition gpg.cpp:121
bool gpg_generate_key(uint8_t curve)
Generates SIG / DEC / AUT keys on the device and announces them to the OpenPGP card application (fing...
Definition gpg.cpp:132
bool gpg_alchemy_fingerprint(char *buf, size_t len)
Writes the alchemical-word fingerprint of the SIG public key.
Definition gpg.cpp:319
bool gpg_get_status(gpg_status_t *status)
Fills status from the OpenPGP card-application state.
Definition gpg.cpp:95
bool gpg_is_initialized(void)
Reports whether at least one OpenPGP key role has a configured fingerprint on the card.
Definition gpg.cpp:91
bool gpg_export_pubkey_pem(char *buf, size_t size, size_t *out_len)
Renders the current SIG public key as a SubjectPublicKeyInfo PEM. The key is read straight from the s...
Definition gpg.cpp:233
bool gpg_reset(void)
Factory-resets all GPG key material and metadata.
Definition gpg.cpp:224
#define GPG_USER_ID_MAX
Definition gpg.h:14
#define GPG_FINGERPRINT_LEN
Definition gpg.h:15
EccCurve
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
bool openpgp_get_fingerprint(uint8_t key_type, uint8_t *fp_out)
Reads the stored OpenPGP v4 fingerprint for a key role.
Definition openpgp.cpp:890
uint32_t openpgp_get_gen_time(uint8_t key_type)
Returns the stored Unix timestamp of key generation, or 0 when unset.
Definition openpgp.cpp:922
#define KEY_AUT
Definition openpgp.h:37
bool openpgp_set_key_fingerprint(uint8_t key_type, const uint8_t *fingerprint, uint32_t gen_time)
Definition openpgp.cpp:949
size_t openpgp_get_cardholder_name(char *out, size_t out_size)
Copies the cardholder name (OpenPGP DO 0x5B) into the caller buffer. Format is gpg's "Surname<<Firstn...
Definition openpgp.cpp:913
bool openpgp_set_cardholder_name(const char *name)
Sets the cardholder name (OpenPGP DO 0x5B) and persists state.
Definition openpgp.cpp:936
#define KEY_SIG
Definition openpgp.h:35
uint32_t openpgp_get_sig_count(void)
Definition openpgp.cpp:886
bool openpgp_has_any_key(void)
Reports whether any of the SIG / DEC / AUT roles has a non-zero fingerprint configured....
Definition openpgp.cpp:907
void openpgp_factory_reset(void)
Definition openpgp.cpp:2218
#define KEY_DEC
Definition openpgp.h:36