CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
xsig.cpp
Go to the documentation of this file.
1#include "xsig.h"
2#include "fingerprint.h"
3#include "mod_gpg/gpg.h"
9#include "cdc_core/Raii.h"
10#include "cdc_log.h"
11
12#include <mbedtls/base64.h>
13#include <mbedtls/sha256.h>
14
15#include <algorithm>
16#include <cstdio>
17#include <cstring>
18
19namespace cdc::mod_gpg {
20
21namespace {
22
23constexpr const char* TAG = "GPG_XSIG";
24
25static const uint8_t kOidEd25519[] = {0x09, 0x2B, 0x06, 0x01, 0x04, 0x01,
26 0xDA, 0x47, 0x0F, 0x01};
27static const uint8_t kOidP256[] = {0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D,
28 0x03, 0x01, 0x07};
29
30constexpr uint8_t kHashAlgoSha256 = 0x08;
31constexpr uint8_t kSigTypeGenericCert = 0x10;
32constexpr uint8_t kSigTypeSubkeyBinding = 0x18;
33constexpr uint8_t kSubpktTypeSigCreated = 0x02;
34constexpr uint8_t kSubpktTypeIssuerKeyId = 0x10;
35constexpr uint8_t kSubpktTypeKeyFlags = 0x1B;
36constexpr uint8_t kKeyFlagEncrypt = 0x0C; // encrypt communications + storage
37
39bool isAllZero(const uint8_t* p, size_t n)
40{
41 for (size_t i = 0; i < n; ++i) {
42 if (p[i] != 0) return false;
43 }
44 return true;
45}
46
49size_t buildPubkeyBody(const gpg_recv_key_t& key, uint8_t* out, size_t out_size)
50{
51 if (!out) return 0;
52 const bool is_ed25519 = (key.curve == CDC_CURVE_ED25519);
53 if (is_ed25519 && key.pubkey_len < ED25519_PUBKEY_SIZE) return 0;
54 if (!is_ed25519 && key.pubkey_len < 64) return 0;
55
56 const uint8_t algo = is_ed25519 ? OPENPGP_ALGO_EDDSA : OPENPGP_ALGO_ECDSA;
57 const uint8_t* oid = is_ed25519 ? kOidEd25519 : kOidP256;
58 const size_t oid_len = is_ed25519 ? sizeof(kOidEd25519) : sizeof(kOidP256);
59
60 uint8_t mpi[MPI_FULL_SIZE_P256];
61 size_t mpi_len;
62 if (is_ed25519) {
63 // EdDSA point in OpenPGP native format: 0x40 prefix + 32-byte point,
64 // encoded as a 263-bit MPI (RFC 9580 / 4880-bis).
65 uint16_t bits = 263;
66 mpi[0] = static_cast<uint8_t>((bits >> 8) & 0xFF);
67 mpi[1] = static_cast<uint8_t>(bits & 0xFF);
68 mpi[MPI_HEADER_SIZE] = 0x40;
69 std::memcpy(mpi + MPI_HEADER_SIZE + 1, key.pubkey, ED25519_PUBKEY_SIZE);
71 } else {
72 uint16_t bits = P256_PUBKEY_BITS;
73 mpi[0] = static_cast<uint8_t>((bits >> 8) & 0xFF);
74 mpi[1] = static_cast<uint8_t>(bits & 0xFF);
75 mpi[MPI_HEADER_SIZE] = 0x04;
76 std::memcpy(mpi + MPI_HEADER_SIZE + 1, key.pubkey, 64);
77 mpi_len = MPI_FULL_SIZE_P256;
78 }
79
80 const size_t total = 1 + 4 + 1 + oid_len + mpi_len;
81 if (total > out_size) return 0;
82
83 size_t off = 0;
84 out[off++] = 0x04;
85 out[off++] = (key.created_at >> 24) & 0xFF;
86 out[off++] = (key.created_at >> 16) & 0xFF;
87 out[off++] = (key.created_at >> 8) & 0xFF;
88 out[off++] = key.created_at & 0xFF;
89 out[off++] = algo;
90 std::memcpy(out + off, oid, oid_len);
91 off += oid_len;
92 std::memcpy(out + off, mpi, mpi_len);
93 off += mpi_len;
94 return off;
95}
96
102size_t buildSigSubpackets(bool hashed,
103 const uint8_t fp_self[20],
104 uint32_t sig_creation_time,
105 uint8_t* out, size_t out_size)
106{
107 if (!out) return 0;
108 if (hashed) {
109 if (out_size < 6) return 0;
110 // Sig creation time: length=5, type=0x02, 4-byte timestamp
111 out[0] = 5;
112 out[1] = kSubpktTypeSigCreated;
113 out[2] = (sig_creation_time >> 24) & 0xFF;
114 out[3] = (sig_creation_time >> 16) & 0xFF;
115 out[4] = (sig_creation_time >> 8) & 0xFF;
116 out[5] = sig_creation_time & 0xFF;
117 return 6;
118 }
119 // Issuer key ID: length=9, type=0x10, 8 bytes = last 8 of own fingerprint
120 if (out_size < 10) return 0;
121 out[0] = 9;
122 out[1] = kSubpktTypeIssuerKeyId;
123 std::memcpy(out + 2, fp_self + 12, 8);
124 return 10;
125}
126
128size_t writeNewFormatLength(uint8_t* out, size_t len)
129{
130 out[0] = 0xFF;
131 out[1] = (len >> 24) & 0xFF;
132 out[2] = (len >> 16) & 0xFF;
133 out[3] = (len >> 8) & 0xFF;
134 out[4] = len & 0xFF;
135 return 5;
136}
137
141size_t writeMpi(const uint8_t* data, size_t len, uint8_t* out, size_t out_size)
142{
143 // Skip leading zero bytes to find the real bit length.
144 size_t start = 0;
145 while (start < len && data[start] == 0) ++start;
146 const size_t real_len = len - start;
147 if (real_len == 0) {
148 if (out_size < 2) return 0;
149 out[0] = 0;
150 out[1] = 0;
151 return 2;
152 }
153 if (out_size < 2 + real_len) return 0;
154
155 uint16_t bits = static_cast<uint16_t>(real_len) * 8;
156 uint8_t high = data[start];
157 for (int b = 7; b >= 0; --b) {
158 if (high & (1u << b)) break;
159 --bits;
160 }
161 out[0] = (bits >> 8) & 0xFF;
162 out[1] = bits & 0xFF;
163 std::memcpy(out + 2, data + start, real_len);
164 return 2 + real_len;
165}
166
168uint32_t crc24(const uint8_t* data, size_t len)
169{
170 constexpr uint32_t kCrc24Init = 0x00B704CEu;
171 constexpr uint32_t kCrc24Poly = 0x01864CFBu;
172 uint32_t crc = kCrc24Init;
173 for (size_t i = 0; i < len; ++i) {
174 crc ^= static_cast<uint32_t>(data[i]) << 16;
175 for (int b = 0; b < 8; ++b) {
176 crc <<= 1;
177 if (crc & 0x01000000u) crc ^= kCrc24Poly;
178 }
179 }
180 return crc & 0x00FFFFFFu;
181}
182
184size_t armorBase64(const uint8_t* data, size_t len, char* out, size_t out_size)
185{
186 size_t enc_len = 0;
187 if (mbedtls_base64_encode(reinterpret_cast<unsigned char*>(out), out_size,
188 &enc_len, data, len) != 0) {
189 return 0;
190 }
191 // mbedtls base64 emits a flat string; we need 64-char-wrapped lines.
192 // Walk backwards to insert \r\n every 64 chars.
193 char tmp[2048];
194 if (enc_len + (enc_len / 64) * 2 + 4 > sizeof(tmp)) return 0;
195
196 size_t out_idx = 0;
197 for (size_t i = 0; i < enc_len; i += 64) {
198 size_t chunk = std::min<size_t>(64, enc_len - i);
199 std::memcpy(tmp + out_idx, out + i, chunk);
200 out_idx += chunk;
201 tmp[out_idx++] = '\r';
202 tmp[out_idx++] = '\n';
203 }
204 if (out_idx > out_size) return 0;
205 std::memcpy(out, tmp, out_idx);
206 return out_idx;
207}
208
212bool armorBinaryBlock(const uint8_t* binary, size_t binary_off,
213 char* out, size_t out_size, size_t* out_len)
214{
215 static constexpr const char* kBegin = "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\n";
216 static constexpr const char* kEnd = "-----END PGP PUBLIC KEY BLOCK-----\r\n";
217 const size_t begin_len = std::strlen(kBegin);
218 const size_t end_len = std::strlen(kEnd);
219
220 if (begin_len + end_len + (binary_off * 2) + 16 > out_size) return false;
221
222 size_t off = 0;
223 std::memcpy(out + off, kBegin, begin_len);
224 off += begin_len;
225
226 size_t body_written = armorBase64(binary, binary_off, out + off, out_size - off);
227 if (body_written == 0) return false;
228 off += body_written;
229
230 // CRC24 line: "=" + 4 base64 chars + CRLF.
231 uint8_t crc_bytes[3];
232 uint32_t crc = crc24(binary, binary_off);
233 crc_bytes[0] = (crc >> 16) & 0xFF;
234 crc_bytes[1] = (crc >> 8) & 0xFF;
235 crc_bytes[2] = crc & 0xFF;
236
237 if (off + 8 > out_size) return false;
238 out[off++] = '=';
239 size_t crc_len = 0;
240 if (mbedtls_base64_encode(reinterpret_cast<unsigned char*>(out + off),
241 out_size - off, &crc_len,
242 crc_bytes, sizeof(crc_bytes)) != 0) {
243 return false;
244 }
245 off += crc_len;
246 out[off++] = '\r';
247 out[off++] = '\n';
248
249 if (off + end_len > out_size) return false;
250 std::memcpy(out + off, kEnd, end_len);
251 off += end_len;
252
253 if (off < out_size) out[off] = '\0';
254 *out_len = off;
255 return true;
256}
257
261size_t buildUidCertPacket(const uint8_t* pk_body, size_t pk_body_len,
262 const char* user_id, size_t uid_len,
263 uint8_t signer_curve, const uint8_t signer_fp[20],
264 uint32_t sig_created_at, const uint8_t sig_rs[64],
265 uint8_t* out, size_t out_size)
266{
267 const uint8_t sig_algo = (signer_curve == CDC_CURVE_ED25519)
269
270 uint8_t hashed_subs[8];
271 const size_t hashed_subs_len = buildSigSubpackets(true, nullptr, sig_created_at,
272 hashed_subs, sizeof(hashed_subs));
273 uint8_t unhashed_subs[10];
274 const size_t unhashed_subs_len = buildSigSubpackets(false, signer_fp, 0,
275 unhashed_subs, sizeof(unhashed_subs));
276
277 if (out_size < 6 + hashed_subs_len + 2 + unhashed_subs_len + 2) return 0;
278
279 size_t off = 0;
280 out[off++] = 0x04;
281 out[off++] = kSigTypeGenericCert;
282 out[off++] = sig_algo;
283 out[off++] = kHashAlgoSha256;
284 out[off++] = (hashed_subs_len >> 8) & 0xFF;
285 out[off++] = hashed_subs_len & 0xFF;
286 std::memcpy(out + off, hashed_subs, hashed_subs_len);
287 off += hashed_subs_len;
288 out[off++] = (unhashed_subs_len >> 8) & 0xFF;
289 out[off++] = unhashed_subs_len & 0xFF;
290 std::memcpy(out + off, unhashed_subs, unhashed_subs_len);
291 off += unhashed_subs_len;
292
293 // Left 16 bits of the signed hash: recompute the hash to obtain them.
294 {
295 const uint8_t pk_prefix[3] = {
296 0x99,
297 static_cast<uint8_t>((pk_body_len >> 8) & 0xFF),
298 static_cast<uint8_t>(pk_body_len & 0xFF),
299 };
300 const uint8_t uid_prefix[5] = {
301 0xB4,
302 static_cast<uint8_t>((uid_len >> 24) & 0xFF),
303 static_cast<uint8_t>((uid_len >> 16) & 0xFF),
304 static_cast<uint8_t>((uid_len >> 8) & 0xFF),
305 static_cast<uint8_t>(uid_len & 0xFF),
306 };
307 uint8_t sig_data_header[6] = {
308 0x04, kSigTypeGenericCert, sig_algo, kHashAlgoSha256,
309 static_cast<uint8_t>((hashed_subs_len >> 8) & 0xFF),
310 static_cast<uint8_t>(hashed_subs_len & 0xFF),
311 };
312 const size_t sig_data_total = sizeof(sig_data_header) + hashed_subs_len;
313 const uint8_t trailer[6] = {
314 0x04, 0xFF,
315 static_cast<uint8_t>((sig_data_total >> 24) & 0xFF),
316 static_cast<uint8_t>((sig_data_total >> 16) & 0xFF),
317 static_cast<uint8_t>((sig_data_total >> 8) & 0xFF),
318 static_cast<uint8_t>(sig_data_total & 0xFF),
319 };
320
321 mbedtls_sha256_context ctx;
322 mbedtls_sha256_init(&ctx);
323 mbedtls_sha256_starts(&ctx, 0);
324 mbedtls_sha256_update(&ctx, pk_prefix, sizeof(pk_prefix));
325 mbedtls_sha256_update(&ctx, pk_body, pk_body_len);
326 mbedtls_sha256_update(&ctx, uid_prefix, sizeof(uid_prefix));
327 mbedtls_sha256_update(&ctx, reinterpret_cast<const uint8_t*>(user_id), uid_len);
328 mbedtls_sha256_update(&ctx, sig_data_header, sizeof(sig_data_header));
329 mbedtls_sha256_update(&ctx, hashed_subs, hashed_subs_len);
330 mbedtls_sha256_update(&ctx, trailer, sizeof(trailer));
331 uint8_t hash[32];
332 mbedtls_sha256_finish(&ctx, hash);
333 mbedtls_sha256_free(&ctx);
334
335 if (off + 2 > out_size) return 0;
336 out[off++] = hash[0];
337 out[off++] = hash[1];
338 }
339
340 size_t mpi_off = writeMpi(sig_rs, 32, out + off, out_size - off);
341 if (mpi_off == 0) return 0;
342 off += mpi_off;
343 mpi_off = writeMpi(sig_rs + 32, 32, out + off, out_size - off);
344 if (mpi_off == 0) return 0;
345 off += mpi_off;
346
347 return off;
348}
349
352size_t buildBindingHashedSubpackets(uint32_t sig_created_at, uint8_t* out)
353{
354 size_t off = 0;
355 out[off++] = 5;
356 out[off++] = kSubpktTypeSigCreated;
357 out[off++] = (sig_created_at >> 24) & 0xFF;
358 out[off++] = (sig_created_at >> 16) & 0xFF;
359 out[off++] = (sig_created_at >> 8) & 0xFF;
360 out[off++] = sig_created_at & 0xFF;
361 out[off++] = 2;
362 out[off++] = kSubpktTypeKeyFlags;
363 out[off++] = kKeyFlagEncrypt;
364 return off;
365}
366
369void hashSubkeyBinding(const uint8_t* primary_body, size_t primary_len,
370 const uint8_t* subkey_body, size_t subkey_len,
371 uint8_t sig_algo,
372 const uint8_t* hashed_subs, size_t hashed_subs_len,
373 uint8_t out_hash[32])
374{
375 const uint8_t sig_data_header[6] = {
376 0x04, kSigTypeSubkeyBinding, sig_algo, kHashAlgoSha256,
377 static_cast<uint8_t>((hashed_subs_len >> 8) & 0xFF),
378 static_cast<uint8_t>(hashed_subs_len & 0xFF),
379 };
380 const size_t sig_data_total = sizeof(sig_data_header) + hashed_subs_len;
381 const uint8_t trailer[6] = {
382 0x04, 0xFF,
383 static_cast<uint8_t>((sig_data_total >> 24) & 0xFF),
384 static_cast<uint8_t>((sig_data_total >> 16) & 0xFF),
385 static_cast<uint8_t>((sig_data_total >> 8) & 0xFF),
386 static_cast<uint8_t>(sig_data_total & 0xFF),
387 };
388 const uint8_t primary_prefix[3] = {
389 0x99,
390 static_cast<uint8_t>((primary_len >> 8) & 0xFF),
391 static_cast<uint8_t>(primary_len & 0xFF),
392 };
393 const uint8_t subkey_prefix[3] = {
394 0x99,
395 static_cast<uint8_t>((subkey_len >> 8) & 0xFF),
396 static_cast<uint8_t>(subkey_len & 0xFF),
397 };
398
399 mbedtls_sha256_context ctx;
400 mbedtls_sha256_init(&ctx);
401 mbedtls_sha256_starts(&ctx, 0);
402 mbedtls_sha256_update(&ctx, primary_prefix, sizeof(primary_prefix));
403 mbedtls_sha256_update(&ctx, primary_body, primary_len);
404 mbedtls_sha256_update(&ctx, subkey_prefix, sizeof(subkey_prefix));
405 mbedtls_sha256_update(&ctx, subkey_body, subkey_len);
406 mbedtls_sha256_update(&ctx, sig_data_header, sizeof(sig_data_header));
407 mbedtls_sha256_update(&ctx, hashed_subs, hashed_subs_len);
408 mbedtls_sha256_update(&ctx, trailer, sizeof(trailer));
409 mbedtls_sha256_finish(&ctx, out_hash);
410 mbedtls_sha256_free(&ctx);
411}
412
415bool signSubkeyBinding(const uint8_t* primary_body, size_t primary_len,
416 const uint8_t* subkey_body, size_t subkey_len,
417 uint32_t sig_created_at, uint8_t out_sig[64])
418{
419 gpg_status_t self_status = {};
420 if (!gpg_get_status(&self_status)) return false;
421 const uint8_t sig_algo = (self_status.curve == CDC_CURVE_ED25519)
423
424 uint8_t hashed_subs[16];
425 const size_t hashed_subs_len = buildBindingHashedSubpackets(sig_created_at, hashed_subs);
426
427 uint8_t hash[32];
428 hashSubkeyBinding(primary_body, primary_len, subkey_body, subkey_len,
429 sig_algo, hashed_subs, hashed_subs_len, hash);
430
432 if (!se) return false;
433 const uint8_t slot = gpg_storage_sig_slot();
434 size_t sig_len = 64;
435 if (sig_algo == OPENPGP_ALGO_EDDSA) {
436 return se->eddsaSign(slot, hash, sizeof(hash), out_sig) == cdc::hal::SeResult::OK;
437 }
438 return se->ecdsaSign(slot, hash, sizeof(hash), out_sig, &sig_len)
440}
441
444size_t buildSubkeyBindingSigPacket(const uint8_t* primary_body, size_t primary_len,
445 const uint8_t* subkey_body, size_t subkey_len,
446 uint8_t signer_curve, const uint8_t signer_fp[20],
447 uint32_t sig_created_at, const uint8_t sig_rs[64],
448 uint8_t* out, size_t out_size)
449{
450 const uint8_t sig_algo = (signer_curve == CDC_CURVE_ED25519)
452
453 uint8_t hashed_subs[16];
454 const size_t hashed_subs_len = buildBindingHashedSubpackets(sig_created_at, hashed_subs);
455 uint8_t unhashed_subs[10];
456 const size_t unhashed_subs_len = buildSigSubpackets(false, signer_fp, 0,
457 unhashed_subs, sizeof(unhashed_subs));
458
459 if (out_size < 6 + hashed_subs_len + 2 + unhashed_subs_len + 2) return 0;
460
461 size_t off = 0;
462 out[off++] = 0x04;
463 out[off++] = kSigTypeSubkeyBinding;
464 out[off++] = sig_algo;
465 out[off++] = kHashAlgoSha256;
466 out[off++] = (hashed_subs_len >> 8) & 0xFF;
467 out[off++] = hashed_subs_len & 0xFF;
468 std::memcpy(out + off, hashed_subs, hashed_subs_len);
469 off += hashed_subs_len;
470 out[off++] = (unhashed_subs_len >> 8) & 0xFF;
471 out[off++] = unhashed_subs_len & 0xFF;
472 std::memcpy(out + off, unhashed_subs, unhashed_subs_len);
473 off += unhashed_subs_len;
474
475 uint8_t hash[32];
476 hashSubkeyBinding(primary_body, primary_len, subkey_body, subkey_len,
477 sig_algo, hashed_subs, hashed_subs_len, hash);
478 if (off + 2 > out_size) return 0;
479 out[off++] = hash[0];
480 out[off++] = hash[1];
481
482 size_t mpi_off = writeMpi(sig_rs, 32, out + off, out_size - off);
483 if (mpi_off == 0) return 0;
484 off += mpi_off;
485 mpi_off = writeMpi(sig_rs + 32, 32, out + off, out_size - off);
486 if (mpi_off == 0) return 0;
487 off += mpi_off;
488
489 return off;
490}
491
494bool loadOwnSigKey(gpg_recv_key_t& self)
495{
496 gpg_status_t status = {};
497 if (!gpg_get_status(&status)) return false;
498
500 if (!se) return false;
501
502 uint8_t pubkey[64 + 1] = {0};
504 if (se->eccGetPublicKey(gpg_storage_sig_slot(), pubkey, &hal_curve)
506 return false;
507 }
508 const uint8_t curve = (hal_curve == cdc::hal::EccCurve::ED25519) ? CDC_CURVE_ED25519
510 if (curve == CDC_CURVE_P256 && pubkey[0] == 0x04) {
511 std::memmove(pubkey, pubkey + 1, 64);
512 }
513
514 self = gpg_recv_key_t{};
515 self.curve = curve;
516 self.pubkey_len = (curve == CDC_CURVE_ED25519) ? 32 : 64;
517 std::memcpy(self.pubkey, pubkey, self.pubkey_len);
518 self.created_at = status.created_at;
519 std::memcpy(self.fingerprint_v4, status.fingerprint, 20);
520 std::strncpy(self.user_id, status.user_id, sizeof(self.user_id) - 1);
521 return true;
522}
523
528bool buildReceivedKeyArmored(const gpg_recv_key_t& key, bool include_our_cert,
529 char* out, size_t out_size, size_t* out_len)
530{
531 if (!out || !out_len || out_size < 256) return false;
532
533 uint8_t pk_body[128];
534 const size_t pk_body_len = buildPubkeyBody(key, pk_body, sizeof(pk_body));
535 if (pk_body_len == 0) return false;
536 const size_t uid_len = strnlen(key.user_id, sizeof(key.user_id));
537
538 uint8_t binary[1024];
539 size_t binary_off = 0;
540 auto emit = [&](uint8_t tag, const uint8_t* body, size_t len) -> bool {
541 if (binary_off + 1 + 5 + len > sizeof(binary)) return false;
542 binary[binary_off++] = tag;
543 binary_off += writeNewFormatLength(binary + binary_off, len);
544 std::memcpy(binary + binary_off, body, len);
545 binary_off += len;
546 return true;
547 };
548
549 if (!emit(0xC6, pk_body, pk_body_len)) return false;
550 if (!emit(0xCD, reinterpret_cast<const uint8_t*>(key.user_id), uid_len)) return false;
551
552 // Owner's UID self-signature (makes the UID valid and the key usable).
553 if (!isAllZero(key.owner_self_sig, 64)) {
554 uint8_t sig[256];
555 const size_t n = buildUidCertPacket(pk_body, pk_body_len, key.user_id, uid_len,
556 key.curve, key.fingerprint_v4,
557 key.created_at, key.owner_self_sig,
558 sig, sizeof(sig));
559 if (n > 0) emit(0xC2, sig, n);
560 }
561
562 // Our own cross-certification on the UID (web of trust).
563 if (include_our_cert && key.sig_len == 64) {
564 uint8_t sig[256];
565 const size_t n = buildCertSigPacket(key, sig, sizeof(sig));
566 if (n > 0) emit(0xC2, sig, n);
567 }
568
569 // DEC encryption subkey + owner's binding signature.
570 if (!isAllZero(key.pubkey_dec, 64) && !isAllZero(key.dec_binding_sig, 64)) {
571 uint8_t dec_body[128];
572 const size_t dec_len = buildEcdhPubkeyBody(key.pubkey_dec, key.created_at_dec,
573 dec_body, sizeof(dec_body));
574 uint8_t bind[256];
575 size_t bind_len = 0;
576 if (dec_len > 0) {
577 bind_len = buildSubkeyBindingSigPacket(pk_body, pk_body_len, dec_body, dec_len,
578 key.curve, key.fingerprint_v4,
579 key.created_at_dec, key.dec_binding_sig,
580 bind, sizeof(bind));
581 }
582 if (bind_len > 0) {
583 emit(0xCE, dec_body, dec_len);
584 emit(0xC2, bind, bind_len);
585 }
586 }
587
588 return armorBinaryBlock(binary, binary_off, out, out_size, out_len);
589}
590
591} // namespace
592
593bool gpgCrossSign(const gpg_recv_key_t& target,
594 uint32_t sig_creation_time,
595 uint8_t out_sig[64])
596{
597 if (!out_sig) return false;
598
599 gpg_status_t self_status = {};
600 if (!gpg_get_status(&self_status)) {
601 LOG_W(TAG, "gpgCrossSign: own GPG key not available");
602 return false;
603 }
604
605 uint8_t pk_body[128];
606 const size_t pk_body_len = buildPubkeyBody(target, pk_body, sizeof(pk_body));
607 if (pk_body_len == 0) return false;
608
609 const size_t uid_len = strnlen(target.user_id, sizeof(target.user_id));
610
611 // Hashed signature header bytes (the same bytes that are written into
612 // the final packet's hashed area, and also fed into the hash).
613 uint8_t hashed_subs[8];
614 const size_t hashed_subs_len = buildSigSubpackets(
615 true, /*fp_self*/ nullptr, sig_creation_time, hashed_subs, sizeof(hashed_subs));
616
617 const uint8_t sig_algo = (self_status.curve == CDC_CURVE_ED25519)
619
620 uint8_t sig_data_header[6];
621 sig_data_header[0] = 0x04;
622 sig_data_header[1] = kSigTypeGenericCert;
623 sig_data_header[2] = sig_algo;
624 sig_data_header[3] = kHashAlgoSha256;
625 sig_data_header[4] = (hashed_subs_len >> 8) & 0xFF;
626 sig_data_header[5] = hashed_subs_len & 0xFF;
627
628 const size_t sig_data_total = sizeof(sig_data_header) + hashed_subs_len;
629 const uint8_t trailer[6] = {
630 0x04, 0xFF,
631 static_cast<uint8_t>((sig_data_total >> 24) & 0xFF),
632 static_cast<uint8_t>((sig_data_total >> 16) & 0xFF),
633 static_cast<uint8_t>((sig_data_total >> 8) & 0xFF),
634 static_cast<uint8_t>(sig_data_total & 0xFF),
635 };
636
637 // SHA-256 over: 0x99 || pk_body_len(2B) || pk_body
638 // || 0xB4 || uid_len(4B) || uid_body
639 // || sig_data_header || hashed_subs
640 // || trailer
641 mbedtls_sha256_context ctx;
642 mbedtls_sha256_init(&ctx);
643 mbedtls_sha256_starts(&ctx, 0);
644
645 const uint8_t pk_prefix[3] = {
646 0x99,
647 static_cast<uint8_t>((pk_body_len >> 8) & 0xFF),
648 static_cast<uint8_t>(pk_body_len & 0xFF),
649 };
650 mbedtls_sha256_update(&ctx, pk_prefix, sizeof(pk_prefix));
651 mbedtls_sha256_update(&ctx, pk_body, pk_body_len);
652
653 const uint8_t uid_prefix[5] = {
654 0xB4,
655 static_cast<uint8_t>((uid_len >> 24) & 0xFF),
656 static_cast<uint8_t>((uid_len >> 16) & 0xFF),
657 static_cast<uint8_t>((uid_len >> 8) & 0xFF),
658 static_cast<uint8_t>(uid_len & 0xFF),
659 };
660 mbedtls_sha256_update(&ctx, uid_prefix, sizeof(uid_prefix));
661 mbedtls_sha256_update(&ctx, reinterpret_cast<const uint8_t*>(target.user_id), uid_len);
662
663 mbedtls_sha256_update(&ctx, sig_data_header, sizeof(sig_data_header));
664 mbedtls_sha256_update(&ctx, hashed_subs, hashed_subs_len);
665 mbedtls_sha256_update(&ctx, trailer, sizeof(trailer));
666
667 uint8_t hash[32];
668 mbedtls_sha256_finish(&ctx, hash);
669 mbedtls_sha256_free(&ctx);
670
672 if (!se) return false;
673 const uint8_t slot = gpg_storage_sig_slot();
674 size_t sig_len = 64;
675
676 if (sig_algo == OPENPGP_ALGO_EDDSA) {
677 return se->eddsaSign(slot, hash, sizeof(hash), out_sig) == cdc::hal::SeResult::OK;
678 }
679 return se->ecdsaSign(slot, hash, sizeof(hash), out_sig, &sig_len)
681}
682
683size_t buildCertSigPacket(const gpg_recv_key_t& key, uint8_t* out, size_t out_size)
684{
685 if (!out || key.sig_len != 64) return 0;
686
687 gpg_status_t self_status = {};
688 if (!gpg_get_status(&self_status)) return 0;
689
690 uint8_t pk_body[128];
691 const size_t pk_body_len = buildPubkeyBody(key, pk_body, sizeof(pk_body));
692 if (pk_body_len == 0) return 0;
693
694 const size_t uid_len = strnlen(key.user_id, sizeof(key.user_id));
695
696 // Our own SIG key is the signer/issuer of this third-party certification.
697 return buildUidCertPacket(pk_body, pk_body_len, key.user_id, uid_len,
698 self_status.curve, self_status.fingerprint,
699 key.sig_created_at, key.my_signature, out, out_size);
700}
701
703 char* out, size_t out_size,
704 size_t* out_len)
705{
706 return buildReceivedKeyArmored(key, /*include_our_cert=*/true, out, out_size, out_len);
707}
708
710 char* out, size_t out_size,
711 size_t* out_len)
712{
713 return buildReceivedKeyArmored(key, /*include_our_cert=*/false, out, out_size, out_len);
714}
715
716bool gpgBuildOwnSignedKeyArmored(char* out, size_t out_size, size_t* out_len)
717{
718 if (!out || !out_len || out_size < 256) return false;
719
720 gpg_recv_key_t self = {};
721 if (!loadOwnSigKey(self)) return false;
722
723 uint8_t pk_body[128];
724 const size_t pk_body_len = buildPubkeyBody(self, pk_body, sizeof(pk_body));
725 if (pk_body_len == 0) return false;
726 const size_t uid_len = strnlen(self.user_id, sizeof(self.user_id));
727
728 constexpr size_t kBinaryCap = 3072;
729 auto binary = ::cdc::core::psramAlloc<uint8_t>(kBinaryCap);
730 if (!binary) return false;
731 size_t binary_off = 0;
732 auto emit = [&](uint8_t tag, const uint8_t* body, size_t len) -> bool {
733 if (binary_off + 1 + 5 + len > kBinaryCap) return false;
734 binary[binary_off++] = tag;
735 binary_off += writeNewFormatLength(binary.get() + binary_off, len);
736 std::memcpy(binary.get() + binary_off, body, len);
737 binary_off += len;
738 return true;
739 };
740
741 if (!emit(0xC6, pk_body, pk_body_len)) return false;
742 if (!emit(0xCD, reinterpret_cast<const uint8_t*>(self.user_id), uid_len)) return false;
743
744 // Self-signature (Tag 2): certify our own UID with our own SIG key. Without
745 // it gpg reports the UID as unsigned and the key as unusable. The signature
746 // creation time is the key creation time, for a deterministic export.
747 uint8_t self_sig[64];
748 if (gpgCrossSign(self, self.created_at, self_sig)) {
749 self.sig_len = 64;
750 self.sig_created_at = self.created_at;
751 std::memcpy(self.my_signature, self_sig, sizeof(self_sig));
752 uint8_t sig_body[256];
753 const size_t sig_body_off = buildCertSigPacket(self, sig_body, sizeof(sig_body));
754 if (sig_body_off > 0) emit(0xC2, sig_body, sig_body_off);
755 }
756
757 // One Signature Packet (Tag 2, new format) per stored third-party cert.
758 auto& store = GpgSelfCertStore::instance();
759 const uint8_t n = store.count();
760 for (uint8_t i = 0; i < n; ++i) {
761 gpg_self_cert_t cert;
762 if (!store.getCert(i, &cert)) continue;
763 if (cert.sig_pkt_len == 0 || cert.sig_pkt_len > kGpgSelfCertSigMax) continue;
764 if (!emit(0xC2, cert.sig_pkt, cert.sig_pkt_len)) break;
765 }
766
767 // Encryption subkey (DEC, RFC 6637 ECDH P-256) + its binding signature.
768 uint8_t dec65[P256_PUBKEY_SIZE] = {0};
769 if (gpg_get_dec_pubkey(dec65)) {
770 const uint32_t dec_time = openpgp_get_gen_time(KEY_DEC);
771 uint8_t dec_body[128];
772 const size_t dec_len = buildEcdhPubkeyBody(dec65 + 1, dec_time,
773 dec_body, sizeof(dec_body));
774 uint8_t binding_sig[64];
775 uint8_t bind[256];
776 size_t bind_len = 0;
777 if (dec_len > 0 &&
778 signSubkeyBinding(pk_body, pk_body_len, dec_body, dec_len, dec_time, binding_sig)) {
779 bind_len = buildSubkeyBindingSigPacket(pk_body, pk_body_len, dec_body, dec_len,
780 self.curve, self.fingerprint_v4,
781 dec_time, binding_sig, bind, sizeof(bind));
782 }
783 if (bind_len > 0) {
784 emit(0xCE, dec_body, dec_len);
785 emit(0xC2, bind, bind_len);
786 }
787 }
788
789 return armorBinaryBlock(binary.get(), binary_off, out, out_size, out_len);
790}
791
792bool gpgBuildOwnSubkeyMaterial(uint8_t dec_pubkey[64], uint32_t* dec_created_at,
793 uint8_t self_sig[64], uint8_t binding_sig[64])
794{
795 if (!dec_pubkey || !self_sig || !binding_sig) return false;
796
797 gpg_recv_key_t self = {};
798 if (!loadOwnSigKey(self)) return false;
799
800 // UID self-signature over our own primary key.
801 if (!gpgCrossSign(self, self.created_at, self_sig)) return false;
802
803 // DEC encryption subkey public point and creation time.
804 uint8_t dec65[P256_PUBKEY_SIZE] = {0};
805 if (!gpg_get_dec_pubkey(dec65)) return false;
806 std::memcpy(dec_pubkey, dec65 + 1, 64);
807 const uint32_t dec_time = openpgp_get_gen_time(KEY_DEC);
808 if (dec_created_at) *dec_created_at = dec_time;
809
810 uint8_t pk_body[128];
811 const size_t pk_len = buildPubkeyBody(self, pk_body, sizeof(pk_body));
812 uint8_t dec_body[128];
813 const size_t dec_len = buildEcdhPubkeyBody(dec_pubkey, dec_time, dec_body, sizeof(dec_body));
814 if (pk_len == 0 || dec_len == 0) return false;
815
816 return signSubkeyBinding(pk_body, pk_len, dec_body, dec_len, dec_time, binding_sig);
817}
818
819} // namespace cdc::mod_gpg
static const char * TAG
uint8_t gpg_storage_sig_slot(void)
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
static GpgSelfCertStore & instance()
#define ED25519_PUBKEY_SIZE
Ed25519 raw public key size in bytes.
Definition constants.h:74
#define OPENPGP_ALGO_ECDSA
OpenPGP algorithm ID for ECDSA (RFC 4880 Section 9.1).
Definition constants.h:29
#define P256_PUBKEY_BITS
Canonical OpenPGP MPI bit-length of an uncompressed P-256 point.
Definition constants.h:69
#define P256_PUBKEY_SIZE
P-256 uncompressed public key size: 0x04 || X(32) || Y(32).
Definition constants.h:53
#define MPI_HEADER_SIZE
OpenPGP MPI header size (2-byte length prefix).
Definition constants.h:79
#define OPENPGP_ALGO_EDDSA
OpenPGP algorithm ID for EdDSA (Ed25519).
Definition constants.h:32
#define MPI_FULL_SIZE_P256
Full P-256 MPI buffer size: 2 byte length prefix + 65 byte uncompressed key.
Definition constants.h:85
#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_get_status(gpg_status_t *status)
Fills status from the OpenPGP card-application state.
Definition gpg.cpp:95
bool gpg_get_dec_pubkey(uint8_t *pub65)
Derives the DEC (decryption) public key as an uncompressed P-256 point.
Definition gpg.cpp:325
PsramUniquePtr< T > psramAlloc(std::size_t count) noexcept
Allocate count elements of T in PSRAM (8-bit capable region).
Definition Raii.h:51
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
size_t buildCertSigPacket(const gpg_recv_key_t &key, uint8_t *out, size_t out_size)
Build the OpenPGP Signature Packet body certifying key.
Definition xsig.cpp:683
bool gpgBuildOwnSubkeyMaterial(uint8_t dec_pubkey[64], uint32_t *dec_created_at, uint8_t self_sig[64], uint8_t binding_sig[64])
Gather the badge's own encryption-subkey material for transfer.
Definition xsig.cpp:792
constexpr size_t kGpgSelfCertSigMax
Maximum length of a stored certification signature packet body (Tag 2).
bool gpgBuildOwnSignedKeyArmored(char *out, size_t out_size, size_t *out_len)
Build an ASCII-armored OpenPGP block for the badge's own public key, appending every stored third-par...
Definition xsig.cpp:716
bool gpgCrossSign(const gpg_recv_key_t &target, uint32_t sig_creation_time, uint8_t out_sig[64])
Cross-sign a received key with the badge's own SIG ECC slot.
Definition xsig.cpp:593
bool gpgBuildPublicKeyArmored(const gpg_recv_key_t &key, char *out, size_t out_size, size_t *out_len)
Build an ASCII-armored OpenPGP public-key block (no signature).
Definition xsig.cpp:709
size_t buildEcdhPubkeyBody(const uint8_t *pubkey, uint32_t created_at, uint8_t *out, size_t out_size)
Serialise an RFC 6637 ECDH Public Key Packet body (algorithm 18).
bool gpgBuildReceivedKeyArmored(const gpg_recv_key_t &key, char *out, size_t out_size, size_t *out_len)
Build an ASCII-armored OpenPGP block for a received peer key.
Definition xsig.cpp:702
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_DEC
Definition openpgp.h:36
One GPG public key received from another badge.
One third-party certification received over the badge's own key.
uint8_t sig_pkt[kGpgSelfCertSigMax]
uint16_t sig_pkt_len
Length of sig_pkt.
Snapshot of the current OpenPGP card-application state for UI display.
Definition gpg.h:25
uint8_t fingerprint[20]
Definition gpg.h:29