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"
7#include "cdc_log.h"
8
9#include <mbedtls/base64.h>
10#include <mbedtls/sha256.h>
11
12#include <algorithm>
13#include <cstdio>
14#include <cstring>
15
16namespace cdc::mod_gpg {
17
18namespace {
19
20constexpr const char* TAG = "GPG_XSIG";
21
22static const uint8_t kOidEd25519[] = {0x09, 0x2B, 0x06, 0x01, 0x04, 0x01,
23 0xDA, 0x47, 0x0F, 0x01};
24static const uint8_t kOidP256[] = {0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D,
25 0x03, 0x01, 0x07};
26
27constexpr uint8_t kHashAlgoSha256 = 0x08;
28constexpr uint8_t kSigTypeGenericCert = 0x10;
29constexpr uint8_t kSubpktTypeSigCreated = 0x02;
30constexpr uint8_t kSubpktTypeIssuerKeyId = 0x10;
31
34size_t buildPubkeyBody(const gpg_recv_key_t& key, uint8_t* out, size_t out_size)
35{
36 if (!out) return 0;
37 const bool is_ed25519 = (key.curve == CDC_CURVE_ED25519);
38 if (is_ed25519 && key.pubkey_len < ED25519_PUBKEY_SIZE) return 0;
39 if (!is_ed25519 && key.pubkey_len < 64) return 0;
40
41 const uint8_t algo = is_ed25519 ? OPENPGP_ALGO_EDDSA : OPENPGP_ALGO_ECDSA;
42 const uint8_t* oid = is_ed25519 ? kOidEd25519 : kOidP256;
43 const size_t oid_len = is_ed25519 ? sizeof(kOidEd25519) : sizeof(kOidP256);
44
45 uint8_t mpi[MPI_FULL_SIZE_P256];
46 size_t mpi_len;
47 if (is_ed25519) {
48 uint16_t bits = (key.pubkey[0] & 0x80) ? 256 : 255;
49 mpi[0] = static_cast<uint8_t>((bits >> 8) & 0xFF);
50 mpi[1] = static_cast<uint8_t>(bits & 0xFF);
51 std::memcpy(mpi + MPI_HEADER_SIZE, key.pubkey, ED25519_PUBKEY_SIZE);
52 mpi_len = MPI_FULL_SIZE_ED25519;
53 } else {
54 uint16_t bits = P256_PUBKEY_BITS;
55 mpi[0] = static_cast<uint8_t>((bits >> 8) & 0xFF);
56 mpi[1] = static_cast<uint8_t>(bits & 0xFF);
57 mpi[MPI_HEADER_SIZE] = 0x04;
58 std::memcpy(mpi + MPI_HEADER_SIZE + 1, key.pubkey, 64);
59 mpi_len = MPI_FULL_SIZE_P256;
60 }
61
62 const size_t total = 1 + 4 + 1 + oid_len + mpi_len;
63 if (total > out_size) return 0;
64
65 size_t off = 0;
66 out[off++] = 0x04;
67 out[off++] = (key.received_at >> 24) & 0xFF;
68 out[off++] = (key.received_at >> 16) & 0xFF;
69 out[off++] = (key.received_at >> 8) & 0xFF;
70 out[off++] = key.received_at & 0xFF;
71 out[off++] = algo;
72 std::memcpy(out + off, oid, oid_len);
73 off += oid_len;
74 std::memcpy(out + off, mpi, mpi_len);
75 off += mpi_len;
76 return off;
77}
78
84size_t buildSigSubpackets(bool hashed,
85 const uint8_t fp_self[20],
86 uint32_t sig_creation_time,
87 uint8_t* out, size_t out_size)
88{
89 if (!out) return 0;
90 if (hashed) {
91 if (out_size < 6) return 0;
92 // Sig creation time: length=5, type=0x02, 4-byte timestamp
93 out[0] = 5;
94 out[1] = kSubpktTypeSigCreated;
95 out[2] = (sig_creation_time >> 24) & 0xFF;
96 out[3] = (sig_creation_time >> 16) & 0xFF;
97 out[4] = (sig_creation_time >> 8) & 0xFF;
98 out[5] = sig_creation_time & 0xFF;
99 return 6;
100 }
101 // Issuer key ID: length=9, type=0x10, 8 bytes = last 8 of own fingerprint
102 if (out_size < 10) return 0;
103 out[0] = 9;
104 out[1] = kSubpktTypeIssuerKeyId;
105 std::memcpy(out + 2, fp_self + 12, 8);
106 return 10;
107}
108
110size_t writeNewFormatLength(uint8_t* out, size_t len)
111{
112 out[0] = 0xFF;
113 out[1] = (len >> 24) & 0xFF;
114 out[2] = (len >> 16) & 0xFF;
115 out[3] = (len >> 8) & 0xFF;
116 out[4] = len & 0xFF;
117 return 5;
118}
119
123size_t writeMpi(const uint8_t* data, size_t len, uint8_t* out, size_t out_size)
124{
125 // Skip leading zero bytes to find the real bit length.
126 size_t start = 0;
127 while (start < len && data[start] == 0) ++start;
128 const size_t real_len = len - start;
129 if (real_len == 0) {
130 if (out_size < 2) return 0;
131 out[0] = 0;
132 out[1] = 0;
133 return 2;
134 }
135 if (out_size < 2 + real_len) return 0;
136
137 uint16_t bits = static_cast<uint16_t>(real_len) * 8;
138 uint8_t high = data[start];
139 for (int b = 7; b >= 0; --b) {
140 if (high & (1u << b)) break;
141 --bits;
142 }
143 out[0] = (bits >> 8) & 0xFF;
144 out[1] = bits & 0xFF;
145 std::memcpy(out + 2, data + start, real_len);
146 return 2 + real_len;
147}
148
150uint32_t crc24(const uint8_t* data, size_t len)
151{
152 constexpr uint32_t kCrc24Init = 0x00B704CEu;
153 constexpr uint32_t kCrc24Poly = 0x01864CFBu;
154 uint32_t crc = kCrc24Init;
155 for (size_t i = 0; i < len; ++i) {
156 crc ^= static_cast<uint32_t>(data[i]) << 16;
157 for (int b = 0; b < 8; ++b) {
158 crc <<= 1;
159 if (crc & 0x01000000u) crc ^= kCrc24Poly;
160 }
161 }
162 return crc & 0x00FFFFFFu;
163}
164
166size_t armorBase64(const uint8_t* data, size_t len, char* out, size_t out_size)
167{
168 size_t enc_len = 0;
169 if (mbedtls_base64_encode(reinterpret_cast<unsigned char*>(out), out_size,
170 &enc_len, data, len) != 0) {
171 return 0;
172 }
173 // mbedtls base64 emits a flat string; we need 64-char-wrapped lines.
174 // Walk backwards to insert \r\n every 64 chars.
175 char tmp[2048];
176 if (enc_len + (enc_len / 64) * 2 + 4 > sizeof(tmp)) return 0;
177
178 size_t out_idx = 0;
179 for (size_t i = 0; i < enc_len; i += 64) {
180 size_t chunk = std::min<size_t>(64, enc_len - i);
181 std::memcpy(tmp + out_idx, out + i, chunk);
182 out_idx += chunk;
183 tmp[out_idx++] = '\r';
184 tmp[out_idx++] = '\n';
185 }
186 if (out_idx > out_size) return 0;
187 std::memcpy(out, tmp, out_idx);
188 return out_idx;
189}
190
191} // namespace
192
193bool gpgCrossSign(const gpg_recv_key_t& target,
194 uint32_t sig_creation_time,
195 uint8_t out_sig[64])
196{
197 if (!out_sig) return false;
198
199 gpg_status_t self_status = {};
200 if (!gpg_get_status(&self_status)) {
201 LOG_W(TAG, "gpgCrossSign: own GPG key not available");
202 return false;
203 }
204
205 uint8_t pk_body[128];
206 const size_t pk_body_len = buildPubkeyBody(target, pk_body, sizeof(pk_body));
207 if (pk_body_len == 0) return false;
208
209 const size_t uid_len = strnlen(target.user_id, sizeof(target.user_id));
210
211 // Hashed signature header bytes (the same bytes that are written into
212 // the final packet's hashed area, and also fed into the hash).
213 uint8_t hashed_subs[8];
214 const size_t hashed_subs_len = buildSigSubpackets(
215 true, /*fp_self*/ nullptr, sig_creation_time, hashed_subs, sizeof(hashed_subs));
216
217 const uint8_t sig_algo = (self_status.curve == CDC_CURVE_ED25519)
219
220 uint8_t sig_data_header[6];
221 sig_data_header[0] = 0x04;
222 sig_data_header[1] = kSigTypeGenericCert;
223 sig_data_header[2] = sig_algo;
224 sig_data_header[3] = kHashAlgoSha256;
225 sig_data_header[4] = (hashed_subs_len >> 8) & 0xFF;
226 sig_data_header[5] = hashed_subs_len & 0xFF;
227
228 const size_t sig_data_total = sizeof(sig_data_header) + hashed_subs_len;
229 const uint8_t trailer[6] = {
230 0x04, 0xFF,
231 static_cast<uint8_t>((sig_data_total >> 24) & 0xFF),
232 static_cast<uint8_t>((sig_data_total >> 16) & 0xFF),
233 static_cast<uint8_t>((sig_data_total >> 8) & 0xFF),
234 static_cast<uint8_t>(sig_data_total & 0xFF),
235 };
236
237 // SHA-256 over: 0x99 || pk_body_len(2B) || pk_body
238 // || 0xB4 || uid_len(4B) || uid_body
239 // || sig_data_header || hashed_subs
240 // || trailer
241 mbedtls_sha256_context ctx;
242 mbedtls_sha256_init(&ctx);
243 mbedtls_sha256_starts(&ctx, 0);
244
245 const uint8_t pk_prefix[3] = {
246 0x99,
247 static_cast<uint8_t>((pk_body_len >> 8) & 0xFF),
248 static_cast<uint8_t>(pk_body_len & 0xFF),
249 };
250 mbedtls_sha256_update(&ctx, pk_prefix, sizeof(pk_prefix));
251 mbedtls_sha256_update(&ctx, pk_body, pk_body_len);
252
253 const uint8_t uid_prefix[5] = {
254 0xB4,
255 static_cast<uint8_t>((uid_len >> 24) & 0xFF),
256 static_cast<uint8_t>((uid_len >> 16) & 0xFF),
257 static_cast<uint8_t>((uid_len >> 8) & 0xFF),
258 static_cast<uint8_t>(uid_len & 0xFF),
259 };
260 mbedtls_sha256_update(&ctx, uid_prefix, sizeof(uid_prefix));
261 mbedtls_sha256_update(&ctx, reinterpret_cast<const uint8_t*>(target.user_id), uid_len);
262
263 mbedtls_sha256_update(&ctx, sig_data_header, sizeof(sig_data_header));
264 mbedtls_sha256_update(&ctx, hashed_subs, hashed_subs_len);
265 mbedtls_sha256_update(&ctx, trailer, sizeof(trailer));
266
267 uint8_t hash[32];
268 mbedtls_sha256_finish(&ctx, hash);
269 mbedtls_sha256_free(&ctx);
270
272 if (!se) return false;
273 const uint8_t slot = gpg_storage_sig_slot();
274 size_t sig_len = 64;
275
276 if (sig_algo == OPENPGP_ALGO_EDDSA) {
277 return se->eddsaSign(slot, hash, sizeof(hash), out_sig) == cdc::hal::SeResult::OK;
278 }
279 return se->ecdsaSign(slot, hash, sizeof(hash), out_sig, &sig_len)
281}
282
284 char* out, size_t out_size,
285 size_t* out_len)
286{
287 if (!out || !out_len || out_size < 256) return false;
288 if (key.sig_len != 64) return false;
289
290 gpg_status_t self_status = {};
291 if (!gpg_get_status(&self_status)) return false;
292
293 uint8_t self_fp_v4[20] = {0};
294 std::memcpy(self_fp_v4, self_status.fingerprint, 20);
295
296 // Build the public-key packet body for the *target*.
297 uint8_t pk_body[128];
298 const size_t pk_body_len = buildPubkeyBody(key, pk_body, sizeof(pk_body));
299 if (pk_body_len == 0) return false;
300
301 const size_t uid_len = strnlen(key.user_id, sizeof(key.user_id));
302 const uint8_t sig_algo = (self_status.curve == CDC_CURVE_ED25519)
304
305 // Hashed + unhashed subpacket blobs.
306 uint8_t hashed_subs[8];
307 const size_t hashed_subs_len = buildSigSubpackets(true, self_fp_v4,
308 key.received_at,
309 hashed_subs, sizeof(hashed_subs));
310 uint8_t unhashed_subs[10];
311 const size_t unhashed_subs_len = buildSigSubpackets(false, self_fp_v4,
312 0,
313 unhashed_subs, sizeof(unhashed_subs));
314
315 // Compose Signature Packet body.
316 uint8_t sig_body[256];
317 size_t sig_body_off = 0;
318 sig_body[sig_body_off++] = 0x04;
319 sig_body[sig_body_off++] = kSigTypeGenericCert;
320 sig_body[sig_body_off++] = sig_algo;
321 sig_body[sig_body_off++] = kHashAlgoSha256;
322 sig_body[sig_body_off++] = (hashed_subs_len >> 8) & 0xFF;
323 sig_body[sig_body_off++] = hashed_subs_len & 0xFF;
324 std::memcpy(sig_body + sig_body_off, hashed_subs, hashed_subs_len);
325 sig_body_off += hashed_subs_len;
326 sig_body[sig_body_off++] = (unhashed_subs_len >> 8) & 0xFF;
327 sig_body[sig_body_off++] = unhashed_subs_len & 0xFF;
328 std::memcpy(sig_body + sig_body_off, unhashed_subs, unhashed_subs_len);
329 sig_body_off += unhashed_subs_len;
330
331 // Left 16 bits of signed hash: recompute the hash to obtain them.
332 {
333 const uint8_t pk_prefix[3] = {
334 0x99,
335 static_cast<uint8_t>((pk_body_len >> 8) & 0xFF),
336 static_cast<uint8_t>(pk_body_len & 0xFF),
337 };
338 const uint8_t uid_prefix[5] = {
339 0xB4,
340 static_cast<uint8_t>((uid_len >> 24) & 0xFF),
341 static_cast<uint8_t>((uid_len >> 16) & 0xFF),
342 static_cast<uint8_t>((uid_len >> 8) & 0xFF),
343 static_cast<uint8_t>(uid_len & 0xFF),
344 };
345 uint8_t sig_data_header[6] = {
346 0x04, kSigTypeGenericCert, sig_algo, kHashAlgoSha256,
347 static_cast<uint8_t>((hashed_subs_len >> 8) & 0xFF),
348 static_cast<uint8_t>(hashed_subs_len & 0xFF),
349 };
350 const size_t sig_data_total = sizeof(sig_data_header) + hashed_subs_len;
351 const uint8_t trailer[6] = {
352 0x04, 0xFF,
353 static_cast<uint8_t>((sig_data_total >> 24) & 0xFF),
354 static_cast<uint8_t>((sig_data_total >> 16) & 0xFF),
355 static_cast<uint8_t>((sig_data_total >> 8) & 0xFF),
356 static_cast<uint8_t>(sig_data_total & 0xFF),
357 };
358
359 mbedtls_sha256_context ctx;
360 mbedtls_sha256_init(&ctx);
361 mbedtls_sha256_starts(&ctx, 0);
362 mbedtls_sha256_update(&ctx, pk_prefix, sizeof(pk_prefix));
363 mbedtls_sha256_update(&ctx, pk_body, pk_body_len);
364 mbedtls_sha256_update(&ctx, uid_prefix, sizeof(uid_prefix));
365 mbedtls_sha256_update(&ctx, reinterpret_cast<const uint8_t*>(key.user_id), uid_len);
366 mbedtls_sha256_update(&ctx, sig_data_header, sizeof(sig_data_header));
367 mbedtls_sha256_update(&ctx, hashed_subs, hashed_subs_len);
368 mbedtls_sha256_update(&ctx, trailer, sizeof(trailer));
369 uint8_t hash[32];
370 mbedtls_sha256_finish(&ctx, hash);
371 mbedtls_sha256_free(&ctx);
372
373 sig_body[sig_body_off++] = hash[0];
374 sig_body[sig_body_off++] = hash[1];
375 }
376
377 // Signature MPIs (R, S).
378 size_t mpi_off = writeMpi(key.my_signature, 32,
379 sig_body + sig_body_off, sizeof(sig_body) - sig_body_off);
380 if (mpi_off == 0) return false;
381 sig_body_off += mpi_off;
382 mpi_off = writeMpi(key.my_signature + 32, 32,
383 sig_body + sig_body_off, sizeof(sig_body) - sig_body_off);
384 if (mpi_off == 0) return false;
385 sig_body_off += mpi_off;
386
387 // Assemble three packets in one buffer.
388 uint8_t binary[1024];
389 size_t binary_off = 0;
390
391 // Public Key Packet (Tag 6, new format).
392 binary[binary_off++] = 0xC6;
393 binary_off += writeNewFormatLength(binary + binary_off, pk_body_len);
394 std::memcpy(binary + binary_off, pk_body, pk_body_len);
395 binary_off += pk_body_len;
396
397 // User ID Packet (Tag 13, new format).
398 binary[binary_off++] = 0xCD;
399 binary_off += writeNewFormatLength(binary + binary_off, uid_len);
400 std::memcpy(binary + binary_off, key.user_id, uid_len);
401 binary_off += uid_len;
402
403 // Signature Packet (Tag 2, new format).
404 binary[binary_off++] = 0xC2;
405 binary_off += writeNewFormatLength(binary + binary_off, sig_body_off);
406 std::memcpy(binary + binary_off, sig_body, sig_body_off);
407 binary_off += sig_body_off;
408
409 // ASCII armor.
410 static constexpr const char* kBegin = "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\n";
411 static constexpr const char* kEnd = "-----END PGP PUBLIC KEY BLOCK-----\r\n";
412 const size_t begin_len = std::strlen(kBegin);
413 const size_t end_len = std::strlen(kEnd);
414
415 if (begin_len + end_len + (binary_off * 2) + 16 > out_size) return false;
416
417 size_t off = 0;
418 std::memcpy(out + off, kBegin, begin_len);
419 off += begin_len;
420
421 size_t body_written = armorBase64(binary, binary_off, out + off, out_size - off);
422 if (body_written == 0) return false;
423 off += body_written;
424
425 // CRC24 line: "=" + 4 base64 chars + CRLF.
426 uint8_t crc_bytes[3];
427 uint32_t crc = crc24(binary, binary_off);
428 crc_bytes[0] = (crc >> 16) & 0xFF;
429 crc_bytes[1] = (crc >> 8) & 0xFF;
430 crc_bytes[2] = crc & 0xFF;
431
432 if (off + 8 > out_size) return false;
433 out[off++] = '=';
434 size_t crc_len = 0;
435 if (mbedtls_base64_encode(reinterpret_cast<unsigned char*>(out + off),
436 out_size - off, &crc_len,
437 crc_bytes, sizeof(crc_bytes)) != 0) {
438 return false;
439 }
440 off += crc_len;
441 out[off++] = '\r';
442 out[off++] = '\n';
443
444 if (off + end_len > out_size) return false;
445 std::memcpy(out + off, kEnd, end_len);
446 off += end_len;
447
448 if (off < out_size) out[off] = '\0';
449 *out_len = off;
450 return true;
451}
452
453} // namespace cdc::mod_gpg
static const char * TAG
uint8_t gpg_storage_sig_slot(void)
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define ED25519_PUBKEY_SIZE
Ed25519 raw public key size in bytes.
Definition constants.h:56
#define MPI_FULL_SIZE_ED25519
Full Ed25519 MPI buffer size: 2 byte length prefix + 32 byte key.
Definition constants.h:64
#define OPENPGP_ALGO_ECDSA
Centralized magic-value constants for the OpenPGP smart-card application.
Definition constants.h:26
#define P256_PUBKEY_BITS
P-256 public key bit-length (used as MPI bit count).
Definition constants.h:51
#define MPI_HEADER_SIZE
OpenPGP MPI header size (2-byte length prefix).
Definition constants.h:61
#define OPENPGP_ALGO_EDDSA
OpenPGP algorithm ID for EdDSA (Ed25519).
Definition constants.h:29
#define MPI_FULL_SIZE_P256
Full P-256 MPI buffer size: 2 byte length prefix + 65 byte uncompressed key.
Definition constants.h:67
#define CDC_CURVE_ED25519
Definition fido2.h:23
bool gpg_get_status(gpg_status_t *status)
Fills status from the OpenPGP card-application state.
Definition gpg.cpp:95
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
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:193
bool gpgBuildSignedKeyArmored(const gpg_recv_key_t &key, char *out, size_t out_size, size_t *out_len)
Build an ASCII-armored OpenPGP block carrying the cross-signed key.
Definition xsig.cpp:283
One GPG public key received from another badge.
Snapshot of the current OpenPGP card-application state for UI display.
Definition gpg.h:25
uint8_t fingerprint[20]
Definition gpg.h:29