CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
fingerprint.cpp
Go to the documentation of this file.
1#include "fingerprint.h"
3#include "mod_gpg/gpg.h"
4
5#include <mbedtls/sha1.h>
6#include <mbedtls/sha256.h>
7
8#include <cstring>
9
10namespace cdc::mod_gpg {
11
12namespace {
13
14static const uint8_t kOidEd25519[] = {0x09, 0x2B, 0x06, 0x01, 0x04, 0x01,
15 0xDA, 0x47, 0x0F, 0x01};
16static const uint8_t kOidP256[] = {0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D,
17 0x03, 0x01, 0x07};
18
21size_t buildPublicKeyBody(uint8_t curve,
22 const uint8_t* pubkey, size_t pubkey_len,
23 uint32_t created_at,
24 uint8_t* out, size_t out_size)
25{
26 if (!out || !pubkey) return 0;
27
28 const bool is_ed25519 = (curve == CDC_CURVE_ED25519);
29
30 if (is_ed25519 && pubkey_len < ED25519_PUBKEY_SIZE) return 0;
31 if (!is_ed25519 && pubkey_len < 64) return 0;
32
33 const uint8_t algo = is_ed25519 ? OPENPGP_ALGO_EDDSA : OPENPGP_ALGO_ECDSA;
34 const uint8_t* oid = is_ed25519 ? kOidEd25519 : kOidP256;
35 const size_t oid_len = is_ed25519 ? sizeof(kOidEd25519) : sizeof(kOidP256);
36
37 uint8_t mpi[MPI_FULL_SIZE_P256];
38 size_t mpi_len;
39 if (is_ed25519) {
40 // RFC 4880-bis: Ed25519 MPI is 256 or 255 bits depending on MSB.
41 uint16_t bits = (pubkey[0] & 0x80) ? 256 : 255;
42 mpi[0] = static_cast<uint8_t>((bits >> 8) & 0xFF);
43 mpi[1] = static_cast<uint8_t>(bits & 0xFF);
44 std::memcpy(mpi + MPI_HEADER_SIZE, pubkey, ED25519_PUBKEY_SIZE);
45 mpi_len = MPI_FULL_SIZE_ED25519;
46 } else {
47 // P-256 MPI: bit-length || 0x04 || X || Y.
48 uint16_t bits = P256_PUBKEY_BITS;
49 mpi[0] = static_cast<uint8_t>((bits >> 8) & 0xFF);
50 mpi[1] = static_cast<uint8_t>(bits & 0xFF);
51 mpi[MPI_HEADER_SIZE] = 0x04;
52 std::memcpy(mpi + MPI_HEADER_SIZE + 1, pubkey, 64);
53 mpi_len = MPI_FULL_SIZE_P256;
54 }
55
56 const size_t total = 1 + 4 + 1 + oid_len + mpi_len;
57 if (total > out_size) return 0;
58
59 size_t off = 0;
60 out[off++] = 0x04;
61 out[off++] = (created_at >> 24) & 0xFF;
62 out[off++] = (created_at >> 16) & 0xFF;
63 out[off++] = (created_at >> 8) & 0xFF;
64 out[off++] = created_at & 0xFF;
65 out[off++] = algo;
66 std::memcpy(out + off, oid, oid_len);
67 off += oid_len;
68 std::memcpy(out + off, mpi, mpi_len);
69 off += mpi_len;
70 return off;
71}
72
73} // namespace
74
76 const uint8_t* pubkey, size_t pubkey_len,
77 uint32_t created_at,
78 uint8_t out_fp[20])
79{
80 if (!out_fp) return false;
81
82 uint8_t body[128];
83 const size_t body_len = buildPublicKeyBody(curve, pubkey, pubkey_len,
84 created_at, body, sizeof(body));
85 if (body_len == 0) return false;
86
87 const uint8_t prefix[3] = {
88 0x99,
89 static_cast<uint8_t>((body_len >> 8) & 0xFF),
90 static_cast<uint8_t>(body_len & 0xFF),
91 };
92
93 mbedtls_sha1_context ctx;
94 mbedtls_sha1_init(&ctx);
95 mbedtls_sha1_starts(&ctx);
96 mbedtls_sha1_update(&ctx, prefix, sizeof(prefix));
97 mbedtls_sha1_update(&ctx, body, body_len);
98 mbedtls_sha1_finish(&ctx, out_fp);
99 mbedtls_sha1_free(&ctx);
100 return true;
101}
102
104 const uint8_t* pubkey, size_t pubkey_len,
105 uint32_t created_at,
106 uint8_t out_fp[32])
107{
108 if (!out_fp) return false;
109
110 uint8_t body[128];
111 const size_t body_len = buildPublicKeyBody(curve, pubkey, pubkey_len,
112 created_at, body, sizeof(body));
113 if (body_len == 0) return false;
114
115 // V5 indicator 0x9A + 4-byte big-endian length, then body, then SHA-256.
116 const uint8_t prefix[5] = {
117 0x9A,
118 static_cast<uint8_t>((body_len >> 24) & 0xFF),
119 static_cast<uint8_t>((body_len >> 16) & 0xFF),
120 static_cast<uint8_t>((body_len >> 8) & 0xFF),
121 static_cast<uint8_t>(body_len & 0xFF),
122 };
123
124 mbedtls_sha256_context ctx;
125 mbedtls_sha256_init(&ctx);
126 mbedtls_sha256_starts(&ctx, 0);
127 mbedtls_sha256_update(&ctx, prefix, sizeof(prefix));
128 mbedtls_sha256_update(&ctx, body, body_len);
129 mbedtls_sha256_finish(&ctx, out_fp);
130 mbedtls_sha256_free(&ctx);
131 return true;
132}
133
134bool gpgCrossSignDigest(const uint8_t fp_v4[20],
135 const char* user_id,
136 uint8_t out_hash[32])
137{
138 if (!fp_v4 || !user_id || !out_hash) return false;
139
140 uint8_t input[84] = {0};
141 std::memcpy(input, fp_v4, 20);
142 const size_t uid_len = std::strlen(user_id);
143 std::memcpy(input + 20, user_id, uid_len > 64 ? 64 : uid_len);
144
145 mbedtls_sha256(input, sizeof(input), out_hash, 0);
146 return true;
147}
148
149} // namespace cdc::mod_gpg
#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
uint8_t curve
uint8_t user_id[FIDO2_USER_ID_MAX_LEN]
bool calculateFingerprintV4(uint8_t curve, const uint8_t *pubkey, size_t pubkey_len, uint32_t created_at, uint8_t out_fp[20])
Compute the RFC 4880 V4 OpenPGP fingerprint (SHA-1, 20 bytes).
bool gpgCrossSignDigest(const uint8_t fp_v4[20], const char *user_id, uint8_t out_hash[32])
Build the digest input for a cross-signature.
bool calculateFingerprintV5(uint8_t curve, const uint8_t *pubkey, size_t pubkey_len, uint32_t created_at, uint8_t out_fp[32])
Compute the V5 / RFC 9580 OpenPGP fingerprint (SHA-256, 32 bytes).