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 // EdDSA point in OpenPGP native format: 0x40 prefix + 32-byte point,
41 // encoded as a 263-bit MPI (RFC 9580 / 4880-bis).
42 uint16_t bits = 263;
43 mpi[0] = static_cast<uint8_t>((bits >> 8) & 0xFF);
44 mpi[1] = static_cast<uint8_t>(bits & 0xFF);
45 mpi[MPI_HEADER_SIZE] = 0x40;
46 std::memcpy(mpi + MPI_HEADER_SIZE + 1, pubkey, ED25519_PUBKEY_SIZE);
48 } else {
49 // P-256 MPI: bit-length || 0x04 || X || Y.
50 uint16_t bits = P256_PUBKEY_BITS;
51 mpi[0] = static_cast<uint8_t>((bits >> 8) & 0xFF);
52 mpi[1] = static_cast<uint8_t>(bits & 0xFF);
53 mpi[MPI_HEADER_SIZE] = 0x04;
54 std::memcpy(mpi + MPI_HEADER_SIZE + 1, pubkey, 64);
55 mpi_len = MPI_FULL_SIZE_P256;
56 }
57
58 const size_t total = 1 + 4 + 1 + oid_len + mpi_len;
59 if (total > out_size) return 0;
60
61 size_t off = 0;
62 out[off++] = 0x04;
63 out[off++] = (created_at >> 24) & 0xFF;
64 out[off++] = (created_at >> 16) & 0xFF;
65 out[off++] = (created_at >> 8) & 0xFF;
66 out[off++] = created_at & 0xFF;
67 out[off++] = algo;
68 std::memcpy(out + off, oid, oid_len);
69 off += oid_len;
70 std::memcpy(out + off, mpi, mpi_len);
71 off += mpi_len;
72 return off;
73}
74
76void v4FpFromBody(const uint8_t* body, size_t body_len, uint8_t out_fp[20])
77{
78 const uint8_t prefix[3] = {
79 0x99,
80 static_cast<uint8_t>((body_len >> 8) & 0xFF),
81 static_cast<uint8_t>(body_len & 0xFF),
82 };
83 mbedtls_sha1_context ctx;
84 mbedtls_sha1_init(&ctx);
85 mbedtls_sha1_starts(&ctx);
86 mbedtls_sha1_update(&ctx, prefix, sizeof(prefix));
87 mbedtls_sha1_update(&ctx, body, body_len);
88 mbedtls_sha1_finish(&ctx, out_fp);
89 mbedtls_sha1_free(&ctx);
90}
91
92} // namespace
93
94size_t buildEcdhPubkeyBody(const uint8_t* pubkey, uint32_t created_at,
95 uint8_t* out, size_t out_size)
96{
97 if (!out || !pubkey) return 0;
98
99 // MPI of the uncompressed point: bit-length || 0x04 || X || Y.
100 uint8_t mpi[MPI_FULL_SIZE_P256];
101 uint16_t bits = P256_PUBKEY_BITS;
102 mpi[0] = static_cast<uint8_t>((bits >> 8) & 0xFF);
103 mpi[1] = static_cast<uint8_t>(bits & 0xFF);
104 mpi[MPI_HEADER_SIZE] = 0x04;
105 std::memcpy(mpi + MPI_HEADER_SIZE + 1, pubkey, 64);
106 const size_t mpi_len = MPI_FULL_SIZE_P256;
107
108 // RFC 6637 KDF parameters: size(0x03) || reserved(0x01) || hash || sym.
109 const uint8_t kdf[4] = {0x03, 0x01, OPENPGP_ECDH_KDF_HASH, OPENPGP_ECDH_KDF_SYM};
110
111 const size_t total = 1 + 4 + 1 + sizeof(kOidP256) + mpi_len + sizeof(kdf);
112 if (total > out_size) return 0;
113
114 size_t off = 0;
115 out[off++] = 0x04;
116 out[off++] = (created_at >> 24) & 0xFF;
117 out[off++] = (created_at >> 16) & 0xFF;
118 out[off++] = (created_at >> 8) & 0xFF;
119 out[off++] = created_at & 0xFF;
120 out[off++] = OPENPGP_ALGO_ECDH;
121 std::memcpy(out + off, kOidP256, sizeof(kOidP256));
122 off += sizeof(kOidP256);
123 std::memcpy(out + off, mpi, mpi_len);
124 off += mpi_len;
125 std::memcpy(out + off, kdf, sizeof(kdf));
126 off += sizeof(kdf);
127 return off;
128}
129
130bool calculateFingerprintV4Ecdh(const uint8_t* pubkey, uint32_t created_at,
131 uint8_t out_fp[20])
132{
133 if (!out_fp || !pubkey) return false;
134 uint8_t body[128];
135 const size_t body_len = buildEcdhPubkeyBody(pubkey, created_at, body, sizeof(body));
136 if (body_len == 0) return false;
137 v4FpFromBody(body, body_len, out_fp);
138 return true;
139}
140
142 const uint8_t* pubkey, size_t pubkey_len,
143 uint32_t created_at,
144 uint8_t out_fp[20])
145{
146 if (!out_fp) return false;
147
148 uint8_t body[128];
149 const size_t body_len = buildPublicKeyBody(curve, pubkey, pubkey_len,
150 created_at, body, sizeof(body));
151 if (body_len == 0) return false;
152
153 v4FpFromBody(body, body_len, out_fp);
154 return true;
155}
156
158 const uint8_t* pubkey, size_t pubkey_len,
159 uint32_t created_at,
160 uint8_t out_fp[32])
161{
162 if (!out_fp) return false;
163
164 uint8_t body[128];
165 const size_t body_len = buildPublicKeyBody(curve, pubkey, pubkey_len,
166 created_at, body, sizeof(body));
167 if (body_len == 0) return false;
168
169 // V5 indicator 0x9A + 4-byte big-endian length, then body, then SHA-256.
170 const uint8_t prefix[5] = {
171 0x9A,
172 static_cast<uint8_t>((body_len >> 24) & 0xFF),
173 static_cast<uint8_t>((body_len >> 16) & 0xFF),
174 static_cast<uint8_t>((body_len >> 8) & 0xFF),
175 static_cast<uint8_t>(body_len & 0xFF),
176 };
177
178 mbedtls_sha256_context ctx;
179 mbedtls_sha256_init(&ctx);
180 mbedtls_sha256_starts(&ctx, 0);
181 mbedtls_sha256_update(&ctx, prefix, sizeof(prefix));
182 mbedtls_sha256_update(&ctx, body, body_len);
183 mbedtls_sha256_finish(&ctx, out_fp);
184 mbedtls_sha256_free(&ctx);
185 return true;
186}
187
188bool gpgCrossSignDigest(const uint8_t fp_v4[20],
189 const char* user_id,
190 uint8_t out_hash[32])
191{
192 if (!fp_v4 || !user_id || !out_hash) return false;
193
194 uint8_t input[84] = {0};
195 std::memcpy(input, fp_v4, 20);
196 const size_t uid_len = std::strlen(user_id);
197 std::memcpy(input + 20, user_id, uid_len > 64 ? 64 : uid_len);
198
199 mbedtls_sha256(input, sizeof(input), out_hash, 0);
200 return true;
201}
202
203} // namespace cdc::mod_gpg
#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 OPENPGP_ECDH_KDF_HASH
KDF hash algorithm ID embedded in an ECDH public key (SHA-256).
Definition constants.h:37
#define MPI_HEADER_SIZE
OpenPGP MPI header size (2-byte length prefix).
Definition constants.h:79
#define OPENPGP_ECDH_KDF_SYM
KEK wrap cipher ID embedded in an ECDH public key (AES-128).
Definition constants.h:40
#define OPENPGP_ALGO_ECDH
Centralized magic-value constants for the OpenPGP smart-card application.
Definition constants.h:26
#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
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 calculateFingerprintV4Ecdh(const uint8_t *pubkey, uint32_t created_at, uint8_t out_fp[20])
Compute the V4 fingerprint of an ECDH (DEC) subkey.
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 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).