Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
address_book_crypto.c
Go to the documentation of this file.
1/*****************************************************************************
2 * (c) 2026 Ledger SAS.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *****************************************************************************/
16
38#include <string.h>
39#include "address_book_crypto.h"
40#include "ledger_account.h"
41#include "identity.h"
42#include "lcx_sha256.h"
43#include "lcx_hmac.h"
44#include "lcx_rng.h"
45#include "os_utils.h"
46#include "io.h"
47#include "crypto_helpers.h"
48
49#ifdef HAVE_ADDRESS_BOOK
50
51/* Private functions ---------------------------------------------------------*/
52
64static bool address_book_derive_hmac_key(const cx_ecfp_256_private_key_t *privkey,
65 const uint8_t *salt,
66 size_t salt_len,
67 uint8_t hmac_key[CX_SHA256_SIZE])
68{
69 cx_sha256_t hash_ctx = {0};
70 uint8_t hash[CX_SHA256_SIZE] = {0};
71 cx_err_t error = CX_INTERNAL_ERROR;
72 bool success = false;
73
74 cx_sha256_init(&hash_ctx);
75 CX_CHECK(cx_hash_no_throw((cx_hash_t *) &hash_ctx, 0, salt, salt_len, NULL, 0));
76 CX_CHECK(cx_hash_no_throw(
77 (cx_hash_t *) &hash_ctx, CX_LAST, privkey->d, privkey->d_len, hash, sizeof(hash)));
78 memmove(hmac_key, hash, CX_SHA256_SIZE);
79 success = true;
80
81end:
82 explicit_bzero(hash, sizeof(hash));
83 return success;
84}
85
99static bool init_hmac_ctx(const path_bip32_t *bip32_path,
100 const uint8_t *salt,
101 size_t salt_len,
102 cx_hmac_sha256_t *hmac_ctx)
103{
104 cx_ecfp_256_private_key_t private_key = {0};
105 uint8_t hmac_key[CX_SHA256_SIZE] = {0};
106 cx_err_t error = CX_INTERNAL_ERROR;
107 bool success = false;
108
109 CX_CHECK(bip32_derive_init_privkey_256(
110 CX_CURVE_SECP256K1, bip32_path->path, bip32_path->length, &private_key, NULL));
111 if (!address_book_derive_hmac_key(&private_key, salt, salt_len, hmac_key)) {
112 goto end;
113 }
114 CX_CHECK(cx_hmac_sha256_init_no_throw(hmac_ctx, hmac_key, CX_SHA256_SIZE));
115 success = true;
116
117end:
118 explicit_bzero(&private_key, sizeof(private_key));
119 explicit_bzero(hmac_key, sizeof(hmac_key));
120 return success;
121}
122
123/* Exported functions --------------------------------------------------------*/
124
134bool address_book_send_hmac_proof(uint8_t type, const uint8_t hmac_proof[CX_SHA256_SIZE])
135{
136 size_t tx = 0;
137
138 // Response format: type(1) | hmac(32)
139 G_io_tx_buffer[tx++] = type;
140 memmove(&G_io_tx_buffer[tx], hmac_proof, CX_SHA256_SIZE);
141 tx += CX_SHA256_SIZE;
142
143 io_send_response_pointer(G_io_tx_buffer, tx, SWO_SUCCESS);
144 return true;
145}
146
158bool address_book_send_register_identity_response(const uint8_t group_handle[GROUP_HANDLE_SIZE],
159 const uint8_t hmac_proof[CX_SHA256_SIZE],
160 const uint8_t hmac_rest[CX_SHA256_SIZE])
161{
162 size_t tx = 0;
163
164 G_io_tx_buffer[tx++] = TYPE_REGISTER_IDENTITY;
165 memmove(&G_io_tx_buffer[tx], group_handle, GROUP_HANDLE_SIZE);
166 tx += GROUP_HANDLE_SIZE;
167 memmove(&G_io_tx_buffer[tx], hmac_proof, CX_SHA256_SIZE);
168 tx += CX_SHA256_SIZE;
169 memmove(&G_io_tx_buffer[tx], hmac_rest, CX_SHA256_SIZE);
170 tx += CX_SHA256_SIZE;
171
172 io_send_response_pointer(G_io_tx_buffer, tx, SWO_SUCCESS);
173 return true;
174}
175
188bool address_book_generate_group_handle(const path_bip32_t *bip32_path,
189 uint8_t group_handle[GROUP_HANDLE_SIZE])
190{
191 static const uint8_t salt[] = "AddressBook-Group";
192 cx_hmac_sha256_t hmac_ctx = {0};
193 cx_err_t error = CX_INTERNAL_ERROR;
194 bool success = false;
195 uint8_t *gid = group_handle;
196 uint8_t *mac = group_handle + GID_SIZE;
197
198 cx_rng_no_throw(gid, GID_SIZE);
199 if (!init_hmac_ctx(bip32_path, salt, sizeof(salt) - 1, &hmac_ctx)) {
200 goto end;
201 }
202 CX_CHECK(
203 cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, CX_LAST, gid, GID_SIZE, mac, CX_SHA256_SIZE));
204 success = true;
205
206end:
207 return success;
208}
209
221bool address_book_verify_group_handle(const path_bip32_t *bip32_path,
222 const uint8_t group_handle[GROUP_HANDLE_SIZE],
223 uint8_t gid_out[GID_SIZE])
224{
225 static const uint8_t salt[] = "AddressBook-Group";
226 cx_hmac_sha256_t hmac_ctx = {0};
227 uint8_t mac_expected[CX_SHA256_SIZE] = {0};
228 cx_err_t error = CX_INTERNAL_ERROR;
229 bool success = false;
230 const uint8_t *gid = group_handle;
231 const uint8_t *mac = group_handle + GID_SIZE;
232
233 if (!init_hmac_ctx(bip32_path, salt, sizeof(salt) - 1, &hmac_ctx)) {
234 goto end;
235 }
236 CX_CHECK(cx_hmac_no_throw(
237 (cx_hmac_t *) &hmac_ctx, CX_LAST, gid, GID_SIZE, mac_expected, CX_SHA256_SIZE));
238 if (os_secure_memcmp(mac, mac_expected, CX_SHA256_SIZE) != 0) {
239 PRINTF("[Address Book] Group handle MAC verification failed\n");
240 goto end;
241 }
242 memmove(gid_out, gid, GID_SIZE);
243 success = true;
244
245end:
246 explicit_bzero(mac_expected, sizeof(mac_expected));
247 return success;
248}
249
262bool address_book_compute_hmac_proof(const path_bip32_t *bip32_path,
263 const uint8_t gid[GID_SIZE],
264 const char *name,
265 uint8_t hmac_out[CX_SHA256_SIZE])
266{
267 static const uint8_t salt[] = "AddressBook-Identity";
268 cx_hmac_sha256_t hmac_ctx = {0};
269 cx_err_t error = CX_INTERNAL_ERROR;
270 bool success = false;
271 uint8_t name_len = (uint8_t) strlen(name);
272
273 if (!init_hmac_ctx(bip32_path, salt, sizeof(salt) - 1, &hmac_ctx)) {
274 goto end;
275 }
276 // gid(32)
277 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, gid, GID_SIZE, NULL, 0));
278 // name_len(1) | name
279 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, &name_len, 1, NULL, 0));
280 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx,
281 CX_LAST,
282 (const uint8_t *) name,
283 name_len,
284 hmac_out,
285 CX_SHA256_SIZE));
286 success = true;
287
288end:
289 return success;
290}
291
301bool address_book_verify_hmac_proof(const path_bip32_t *bip32_path,
302 const uint8_t gid[GID_SIZE],
303 const char *name,
304 const uint8_t hmac_expected[CX_SHA256_SIZE])
305{
306 uint8_t hmac_computed[CX_SHA256_SIZE] = {0};
307 bool success = false;
308
309 if (!address_book_compute_hmac_proof(bip32_path, gid, name, hmac_computed)) {
310 goto end;
311 }
312 if (os_secure_memcmp(hmac_computed, hmac_expected, CX_SHA256_SIZE) != 0) {
313 PRINTF("HMAC_PROOF mismatch\n");
314 goto end;
315 }
316 success = true;
317
318end:
319 explicit_bzero(hmac_computed, sizeof(hmac_computed));
320 return success;
321}
322
340bool address_book_compute_hmac_rest(const path_bip32_t *bip32_path,
341 const uint8_t gid[GID_SIZE],
342 const char *scope,
343 const uint8_t *identifier,
344 uint8_t identifier_len,
345 blockchain_family_e family,
346 uint64_t chain_id,
347 uint8_t hmac_out[CX_SHA256_SIZE])
348{
349 static const uint8_t salt[] = "AddressBook-Identity";
350 cx_hmac_sha256_t hmac_ctx = {0};
351 cx_err_t error = CX_INTERNAL_ERROR;
352 bool success = false;
353 uint8_t scope_len = (scope != NULL) ? (uint8_t) strlen(scope) : 0;
354 uint8_t family_byte = (uint8_t) family;
355
356 if (!init_hmac_ctx(bip32_path, salt, sizeof(salt) - 1, &hmac_ctx)) {
357 goto end;
358 }
359 // gid(32)
360 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, gid, GID_SIZE, NULL, 0));
361 // scope_len(1) | scope
362 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, &scope_len, 1, NULL, 0));
363 if (scope_len > 0) {
364 CX_CHECK(cx_hmac_no_throw(
365 (cx_hmac_t *) &hmac_ctx, 0, (const uint8_t *) scope, scope_len, NULL, 0));
366 }
367 // id_len(1) | identifier
368 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, &identifier_len, 1, NULL, 0));
369 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, identifier, identifier_len, NULL, 0));
370 // family(1)
371 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, &family_byte, 1, NULL, 0));
372 // chain_id(8) — Ethereum only
373 if (family == FAMILY_ETHEREUM) {
374 uint8_t chain_id_be[8] = {0};
375 U8BE_ENCODE(chain_id_be, 0, chain_id);
376 CX_CHECK(cx_hmac_no_throw(
377 (cx_hmac_t *) &hmac_ctx, 0, chain_id_be, sizeof(chain_id_be), NULL, 0));
378 }
379 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, CX_LAST, NULL, 0, hmac_out, CX_SHA256_SIZE));
380 success = true;
381
382end:
383 return success;
384}
385
399bool address_book_verify_hmac_rest(const path_bip32_t *bip32_path,
400 const uint8_t gid[GID_SIZE],
401 const char *scope,
402 const uint8_t *identifier,
403 uint8_t identifier_len,
404 blockchain_family_e family,
405 uint64_t chain_id,
406 const uint8_t hmac_expected[CX_SHA256_SIZE])
407{
408 uint8_t hmac_computed[CX_SHA256_SIZE] = {0};
409 bool success = false;
410
411 if (!address_book_compute_hmac_rest(
412 bip32_path, gid, scope, identifier, identifier_len, family, chain_id, hmac_computed)) {
413 goto end;
414 }
415 if (os_secure_memcmp(hmac_computed, hmac_expected, CX_SHA256_SIZE) != 0) {
416 PRINTF("HMAC_REST mismatch\n");
417 goto end;
418 }
419 success = true;
420
421end:
422 explicit_bzero(hmac_computed, sizeof(hmac_computed));
423 return success;
424}
425
426#ifdef HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT
427
443bool address_book_compute_hmac_proof_ledger_account(const path_bip32_t *bip32_path,
444 const char *name,
445 blockchain_family_e family,
446 uint64_t chain_id,
447 uint8_t hmac_out[CX_SHA256_SIZE])
448{
449 static const uint8_t salt[] = "AddressBook-LedgerAccount";
450 cx_hmac_sha256_t hmac_ctx = {0};
451 cx_err_t error = CX_INTERNAL_ERROR;
452 bool success = false;
453 uint8_t name_len = (uint8_t) strlen(name);
454 uint8_t family_byte = (uint8_t) family;
455
456 if (!init_hmac_ctx(bip32_path, salt, sizeof(salt) - 1, &hmac_ctx)) {
457 goto end;
458 }
459 // name_len(1) | name
460 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, &name_len, 1, NULL, 0));
461 CX_CHECK(
462 cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, (const uint8_t *) name, name_len, NULL, 0));
463 // family(1)
464 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, 0, &family_byte, 1, NULL, 0));
465 // chain_id(8) — Ethereum only
466 if (family == FAMILY_ETHEREUM) {
467 uint8_t chain_id_be[8] = {0};
468 U8BE_ENCODE(chain_id_be, 0, chain_id);
469 CX_CHECK(cx_hmac_no_throw(
470 (cx_hmac_t *) &hmac_ctx, 0, chain_id_be, sizeof(chain_id_be), NULL, 0));
471 }
472 CX_CHECK(cx_hmac_no_throw((cx_hmac_t *) &hmac_ctx, CX_LAST, NULL, 0, hmac_out, CX_SHA256_SIZE));
473 success = true;
474
475end:
476 return success;
477}
478
489bool address_book_verify_hmac_proof_ledger_account(const path_bip32_t *bip32_path,
490 const char *name,
491 blockchain_family_e family,
492 uint64_t chain_id,
493 const uint8_t hmac_expected[CX_SHA256_SIZE])
494{
495 uint8_t hmac_computed[CX_SHA256_SIZE] = {0};
496 bool success = false;
497
498 if (!address_book_compute_hmac_proof_ledger_account(
499 bip32_path, name, family, chain_id, hmac_computed)) {
500 goto end;
501 }
502 if (os_secure_memcmp(hmac_computed, hmac_expected, CX_SHA256_SIZE) != 0) {
503 PRINTF("HMAC proof mismatch\n");
504 goto end;
505 }
506 success = true;
507
508end:
509 explicit_bzero(hmac_computed, sizeof(hmac_computed));
510 return success;
511}
512
513#endif // HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT
514#endif // HAVE_ADDRESS_BOOK
blockchain_family_e
@ FAMILY_ETHEREUM
Register / Edit Contact Name / Edit Scope / Edit Identifier.
#define CX_LAST
Definition lcx_common.h:115
HMAC (Keyed-Hash Message Authentication Code)
Random Number Generation.
SHA-2 (Secure Hash Algorithm 2)
uint32_t path[MAX_BIP32_PATH]
Definition bip32.h:19
uint8_t length
Definition bip32.h:18