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 // Read the actual SIG-key curve from the secure element so EdDSA/ECDSA
109 // labelling and the v4 fingerprint body match the on-card key.
110 uint8_t sig_pub[64] = {0};
111 uint8_t sig_curve = CDC_CURVE_ED25519;
112 status->curve = se_get_pubkey(gpg_storage_sig_slot(), sig_pub, sizeof(sig_pub),
113 &sig_curve)
114 ? sig_curve
116
117 char name[GPG_USER_ID_MAX] = {0};
119 if (name[0]) {
120 strncpy(status->user_id, name, sizeof(status->user_id) - 1);
121 }
122 return true;
123}
124
126 if (!user_id || !user_id[0]) return false;
127 strncpy(s_pending_user_id, user_id, sizeof(s_pending_user_id) - 1);
128 s_pending_user_id[sizeof(s_pending_user_id) - 1] = '\0';
129 return true;
130}
131
133 return s_pending_user_id[0] != '\0';
134}
135
136bool gpg_generate_key(uint8_t curve) {
137 if (!gpg_storage_ready()) {
138 LOG_E(TAG, "generate: storage not ready");
139 return false;
140 }
142 LOG_E(TAG, "generate: missing user-id");
143 return false;
144 }
145
146 uint8_t sig_slot = gpg_storage_sig_slot();
147 uint8_t dec_slot = gpg_storage_dec_slot();
148 uint8_t aut_slot = gpg_storage_aut_slot();
149 LOG_I(TAG, "generate: curve=%u sig_slot=%u dec_slot=%u aut_slot=%u",
150 curve, sig_slot, dec_slot, aut_slot);
151
152 // SIG and AUT keys live on TROPIC01.
153 if (!se_generate_key(sig_slot, curve)) {
154 LOG_E(TAG, "generate: SIG key generation failed");
155 return false;
156 }
157 if (!se_generate_key(aut_slot, curve)) {
158 LOG_E(TAG, "generate: AUT key generation failed");
159 return false;
160 }
161
162 // DEC key uses software ECDH P-256 (TROPIC01 has no ECDH primitive). The
163 // matching ECC slot stays empty; the encrypted private key lives in
164 // R-Memory via gpg_storage_save_dec_privkey().
166 if (se) {
167 se->eccDelete(dec_slot);
168 }
169 uint8_t dec_priv[P256_PRIVKEY_SIZE] = {};
170 uint8_t dec_pub65[P256_PUBKEY_SIZE] = {};
171 if (!ecdh_p256_generate_keypair(dec_priv, dec_pub65)) {
172 LOG_E(TAG, "generate: DEC ECDH keypair failed");
173 return false;
174 }
175 bool dec_saved = gpg_storage_save_dec_privkey(dec_priv, nullptr);
176 mbedtls_platform_zeroize(dec_priv, sizeof(dec_priv));
177 if (!dec_saved) {
178 LOG_E(TAG, "generate: DEC privkey save failed");
179 return false;
180 }
181
182 uint32_t created_at = get_unix_time();
183
184 uint8_t pub_sig[64] = {};
185 uint8_t pub_aut[64] = {};
186 uint8_t curve_sig = curve;
187 uint8_t curve_aut = curve;
188 if (!se_get_pubkey(sig_slot, pub_sig, sizeof(pub_sig), &curve_sig)) {
189 LOG_E(TAG, "generate: read SIG pubkey failed");
190 return false;
191 }
192 if (!se_get_pubkey(aut_slot, pub_aut, sizeof(pub_aut), &curve_aut)) {
193 LOG_E(TAG, "generate: read AUT pubkey failed");
194 return false;
195 }
196
197 uint8_t fp_sig[GPG_FINGERPRINT_LEN] = {};
198 uint8_t fp_dec[GPG_FINGERPRINT_LEN] = {};
199 uint8_t fp_aut[GPG_FINGERPRINT_LEN] = {};
200 if (!calculate_fingerprint(pub_sig, curve_sig, created_at, fp_sig)) {
201 LOG_E(TAG, "generate: SIG fingerprint failed");
202 return false;
203 }
204 // dec_pub65 layout is 0x04 || X || Y. The DEC key is an RFC 6637 ECDH
205 // encryption key, so its fingerprint must be taken over the ECDH public-key
206 // body (algorithm 18 + KDF params), not the ECDSA body, or GnuPG will not
207 // link the exported encryption subkey to the card.
208 if (!::cdc::mod_gpg::calculateFingerprintV4Ecdh(dec_pub65 + 1, created_at, fp_dec)) {
209 LOG_E(TAG, "generate: DEC fingerprint failed");
210 return false;
211 }
212 if (!calculate_fingerprint(pub_aut, curve_aut, created_at, fp_aut)) {
213 LOG_E(TAG, "generate: AUT fingerprint failed");
214 return false;
215 }
216
217 openpgp_set_key_fingerprint(KEY_SIG, fp_sig, created_at);
218 openpgp_set_key_fingerprint(KEY_DEC, fp_dec, created_at);
219 openpgp_set_key_fingerprint(KEY_AUT, fp_aut, created_at);
220
221 if (s_pending_user_id[0]) {
222 openpgp_set_cardholder_name(s_pending_user_id);
223 }
224
225 s_pending_user_id[0] = '\0';
226 LOG_I(TAG, "generate: success");
227 return true;
228}
229
230bool gpg_reset(void) {
231 if (!gpg_storage_ready()) return false;
232 // openpgp_factory_reset() wipes ECC slots, DEC privkey, NVS state
233 // (fingerprints, gen-times, counter, cardholder, RC) and PINs.
235 s_pending_user_id[0] = '\0';
236 return true;
237}
238
239bool gpg_export_pubkey_pem(char *buf, size_t size, size_t *out_len) {
240 if (!buf || size < 256 || !out_len) {
241 LOG_W(TAG, "export: bad args");
242 return false;
243 }
245 LOG_W(TAG, "export: no key configured");
246 return false;
247 }
248
250 if (!se) {
251 LOG_W(TAG, "export: no SE");
252 return false;
253 }
254
255 // Buffer sized for the optional 0x04 SEC1 prefix that secure elements
256 // can prepend on P-256 reads (so 65 bytes for P-256 vs 32 for Ed25519).
257 uint8_t pubkey[P256_PUBKEY_SIZE + 1] = {0};
259 uint8_t sig_slot = gpg_storage_sig_slot();
260 auto res = se->eccGetPublicKey(sig_slot, pubkey, &eccCurve);
261 if (res != cdc::hal::SeResult::OK) {
262 LOG_W(TAG, "export: eccGetPublicKey slot=%u failed (%d)",
263 sig_slot, static_cast<int>(res));
264 return false;
265 }
266 uint8_t curve = (eccCurve == cdc::hal::EccCurve::ED25519) ? CDC_CURVE_ED25519
268 if (curve == CDC_CURVE_P256 && pubkey[0] == 0x04) {
269 memmove(pubkey, pubkey + 1, P256_PUBKEY_SIZE);
270 }
271
272 if (curve == CDC_CURVE_ED25519) {
273 static const uint8_t ed25519_prefix[] = {
274 0x30, 0x2a,
275 0x30, 0x05,
276 0x06, 0x03, 0x2b, 0x65, 0x70,
277 0x03, 0x21, 0x00
278 };
279
280 uint8_t der[sizeof(ed25519_prefix) + 32];
281 memcpy(der, ed25519_prefix, sizeof(ed25519_prefix));
282 memcpy(der + sizeof(ed25519_prefix), pubkey, 32);
283
284 size_t b64_len = 0;
285 char b64[128];
286 if (mbedtls_base64_encode(reinterpret_cast<unsigned char*>(b64), sizeof(b64),
287 &b64_len, der, sizeof(der)) != 0) {
288 return false;
289 }
290
291 int written = snprintf(buf, size,
292 "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
293 b64);
294 if (written < 0 || static_cast<size_t>(written) >= size) return false;
295 *out_len = static_cast<size_t>(written);
296 return true;
297 }
298
299 static const uint8_t p256_prefix[] = {
300 0x30, 0x59,
301 0x30, 0x13,
302 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
303 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
304 0x03, 0x42, 0x00
305 };
306 uint8_t der[sizeof(p256_prefix) + 65];
307 memcpy(der, p256_prefix, sizeof(p256_prefix));
308 der[sizeof(p256_prefix)] = 0x04;
309 memcpy(der + sizeof(p256_prefix) + 1, pubkey, 64);
310
311 size_t b64_len = 0;
312 char b64[200];
313 if (mbedtls_base64_encode(reinterpret_cast<unsigned char*>(b64), sizeof(b64),
314 &b64_len, der, sizeof(der)) != 0) {
315 return false;
316 }
317 int written = snprintf(buf, size,
318 "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
319 b64);
320 if (written < 0 || static_cast<size_t>(written) >= size) return false;
321 *out_len = static_cast<size_t>(written);
322 return true;
323}
324
325bool gpg_get_dec_pubkey(uint8_t *pub65) {
326 if (!pub65) return false;
327 uint8_t priv[P256_PRIVKEY_SIZE] = {};
328 if (!gpg_storage_load_dec_privkey(priv, nullptr)) {
329 LOG_W(TAG, "dec pubkey: load privkey failed");
330 return false;
331 }
332 bool ok = ecdh_p256_derive_pubkey(priv, pub65);
333 mbedtls_platform_zeroize(priv, sizeof(priv));
334 return ok;
335}
336
337bool gpg_alchemy_fingerprint(char *buf, size_t len) {
338 if (!buf || len < KEY_FINGERPRINT_MAX_LEN) {
339 return false;
340 }
342}
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.
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)
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:74
#define P256_PUBKEY_SIZE
P-256 uncompressed public key size: 0x04 || X(32) || Y(32).
Definition constants.h:53
#define P256_PRIVKEY_SIZE
P-256 private key (scalar) size in bytes.
Definition constants.h:56
bool ecdh_p256_generate_keypair(uint8_t *privkey_out, uint8_t *pubkey_out)
Definition ecdh.cpp:153
bool ecdh_p256_derive_pubkey(const uint8_t *privkey, uint8_t *pubkey_out)
Definition ecdh.cpp:214
#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:132
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:125
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:136
bool gpg_alchemy_fingerprint(char *buf, size_t len)
Writes the alchemical-word fingerprint of the SIG public key.
Definition gpg.cpp:337
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:239
bool gpg_get_dec_pubkey(uint8_t *pub65)
Derives the DEC (decryption) public key as an uncompressed P-256 point.
Definition gpg.cpp:325
bool gpg_reset(void)
Factory-resets all GPG key material and metadata.
Definition gpg.cpp:230
#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 calculateFingerprintV4Ecdh(const uint8_t *pubkey, uint32_t created_at, uint8_t out_fp[20])
Compute the V4 fingerprint of an ECDH (DEC) subkey.
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:1064
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:1096
#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:1123
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:1087
bool openpgp_set_cardholder_name(const char *name)
Sets the cardholder name (OpenPGP DO 0x5B) and persists state.
Definition openpgp.cpp:1110
#define KEY_SIG
Definition openpgp.h:35
uint32_t openpgp_get_sig_count(void)
Definition openpgp.cpp:1060
bool openpgp_has_any_key(void)
Reports whether any of the SIG / DEC / AUT roles has a non-zero fingerprint configured....
Definition openpgp.cpp:1081
void openpgp_factory_reset(void)
Definition openpgp.cpp:2729
#define KEY_DEC
Definition openpgp.h:36