Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
identity_register.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
30/* Includes ------------------------------------------------------------------*/
31#include <string.h>
32#include "os_utils.h"
33#include "os_apdu.h"
34#include "status_words.h"
35#include "tlv_library.h"
36#include "buffer.h"
37#include "bip32.h"
38#include "address_book.h"
40#include "address_book_crypto.h"
41#include "address_book_common.h"
42#include "identity.h"
43#include "io.h"
44#include "nbgl_use_case.h"
45
46#if defined(HAVE_ADDRESS_BOOK)
47
48/* Private defines------------------------------------------------------------*/
49#define STRUCT_VERSION 0x01
50
51/* Private enumerations ------------------------------------------------------*/
52
53/* Private types, structures, unions -----------------------------------------*/
54
55typedef struct {
56 s_register_state_t *state;
57 TLV_reception_t received_tags;
58} s_identity_ctx;
59
60/* Private macros-------------------------------------------------------------*/
61#define REGISTER_IDENTITY_TAGS(X) \
62 X(0x01, TAG_STRUCTURE_TYPE, handle_struct_type, ENFORCE_UNIQUE_TAG) \
63 X(0x02, TAG_STRUCTURE_VERSION, handle_struct_version, ENFORCE_UNIQUE_TAG) \
64 X(0xf0, TAG_CONTACT_NAME, handle_contact_name, ENFORCE_UNIQUE_TAG) \
65 X(0xf1, TAG_SCOPE, handle_scope, ENFORCE_UNIQUE_TAG) \
66 X(0xf2, TAG_ACCOUNT_IDENTIFIER, handle_identifier, ENFORCE_UNIQUE_TAG) \
67 X(0x23, TAG_CHAIN_ID, handle_chain_id, ENFORCE_UNIQUE_TAG) \
68 X(0x51, TAG_BLOCKCHAIN_FAMILY, handle_blockchain_family, ENFORCE_UNIQUE_TAG) \
69 X(0xf6, TAG_GROUP_HANDLE, handle_group_handle, ENFORCE_UNIQUE_TAG) \
70 X(0x29, TAG_HMAC_PROOF, handle_hmac_proof, ENFORCE_UNIQUE_TAG)
71
72/* Private variables ---------------------------------------------------------*/
73
74/* Private functions ---------------------------------------------------------*/
75
83static bool handle_struct_type(const tlv_data_t *data, s_identity_ctx *context)
84{
85 UNUSED(context);
86 if (!tlv_enforce_u8_value(data, TYPE_REGISTER_IDENTITY)) {
87 PRINTF("[Register Identity] Invalid STRUCTURE_TYPE value\n");
88 return false;
89 }
90 return true;
91}
92
100static bool handle_struct_version(const tlv_data_t *data, s_identity_ctx *context)
101{
102 UNUSED(context);
103 if (!tlv_enforce_u8_value(data, STRUCT_VERSION)) {
104 PRINTF("[Register Identity] Invalid STRUCTURE_VERSION value\n");
105 return false;
106 }
107 return true;
108}
109
117static bool handle_contact_name(const tlv_data_t *data, s_identity_ctx *context)
118{
119 if (!address_book_handle_printable_string(data,
120 context->state->identity.contact_name,
121 sizeof(context->state->identity.contact_name))) {
122 PRINTF("CONTACT_NAME: failed to parse\n");
123 return false;
124 }
125 return true;
126}
127
135static bool handle_scope(const tlv_data_t *data, s_identity_ctx *context)
136{
137 if (!address_book_handle_printable_string(
138 data, context->state->identity.scope, sizeof(context->state->identity.scope))) {
139 PRINTF("SCOPE: failed to parse\n");
140 return false;
141 }
142 return true;
143}
144
152static bool handle_identifier(const tlv_data_t *data, s_identity_ctx *context)
153{
154 buffer_t buf = {0};
155 if (!get_buffer_from_tlv_data(data, &buf, 1, IDENTIFIER_MAX_LENGTH)) {
156 PRINTF("IDENTIFIER: failed to extract\n");
157 return false;
158 }
159 memmove(context->state->identity.identifier, buf.ptr, buf.size);
160 context->state->identity.identifier_len = (uint8_t) buf.size;
161 return true;
162}
163
171static bool handle_chain_id(const tlv_data_t *data, s_identity_ctx *context)
172{
173 return address_book_handle_chain_id(data, &context->state->identity.chain_id);
174}
175
183static bool handle_blockchain_family(const tlv_data_t *data, s_identity_ctx *context)
184{
185 return address_book_handle_blockchain_family(data, &context->state->identity.blockchain_family);
186}
187
195static bool handle_group_handle(const tlv_data_t *data, s_identity_ctx *context)
196{
197 buffer_t buf = {0};
198 if (!get_buffer_from_tlv_data(data, &buf, GROUP_HANDLE_SIZE, GROUP_HANDLE_SIZE)) {
199 PRINTF("[Register Identity] GROUP_HANDLE: invalid length (expected %d bytes)\n",
200 GROUP_HANDLE_SIZE);
201 return false;
202 }
203 memmove(context->state->group_handle, buf.ptr, GROUP_HANDLE_SIZE);
204 return true;
205}
206
214static bool handle_hmac_proof(const tlv_data_t *data, s_identity_ctx *context)
215{
216 buffer_t buf = {0};
217 if (!get_buffer_from_tlv_data(data, &buf, CX_SHA256_SIZE, CX_SHA256_SIZE)) {
218 PRINTF("[Register Identity] HMAC_PROOF: invalid length (expected %d bytes)\n",
219 CX_SHA256_SIZE);
220 return false;
221 }
222 memmove(context->state->hmac_proof, buf.ptr, CX_SHA256_SIZE);
223 return true;
224}
225
226DEFINE_TLV_PARSER(REGISTER_IDENTITY_TAGS, NULL, identity_tlv_parser)
227
228
234static bool verify_fields(const s_identity_ctx *context)
235{
236 bool result = TLV_CHECK_RECEIVED_TAGS(context->received_tags,
237 TAG_STRUCTURE_TYPE,
238 TAG_STRUCTURE_VERSION,
239 TAG_CONTACT_NAME,
240 TAG_SCOPE,
241 TAG_ACCOUNT_IDENTIFIER,
242 TAG_BLOCKCHAIN_FAMILY);
243 if (!result) {
244 PRINTF("Missing mandatory fields in Register Identity descriptor!\n");
245 return false;
246 }
247 if (context->state->identity.blockchain_family == FAMILY_ETHEREUM) {
248 result = TLV_CHECK_RECEIVED_TAGS(context->received_tags, TAG_CHAIN_ID);
249 if (!result) {
250 PRINTF("Missing CHAIN_ID for Ethereum family in Register Identity descriptor!\n");
251 return false;
252 }
253 }
254 // GROUP_HANDLE and HMAC_PROOF are optional, but must be provided together
255 bool has_group_handle = TLV_CHECK_RECEIVED_TAGS(context->received_tags, TAG_GROUP_HANDLE);
256 bool has_hmac_proof = TLV_CHECK_RECEIVED_TAGS(context->received_tags, TAG_HMAC_PROOF);
257 if (has_group_handle != has_hmac_proof) {
258 PRINTF("GROUP_HANDLE and HMAC_PROOF must be provided together!\n");
259 return false;
260 }
261 return true;
262}
263
270static void print_payload(const s_identity_ctx *context)
271{
272 UNUSED(context);
273 PRINTF("****************************************************************************\n");
274 PRINTF("[Register Identity] - Retrieved Descriptor:\n");
275 PRINTF("[Register Identity] - Contact name: %s\n",
276 context->state->identity.contact_name);
277 if (context->state->identity.scope[0] != '\0') {
278 PRINTF("[Register Identity] - Contact scope: %s\n",
279 context->state->identity.scope);
280 }
281 PRINTF("[Register Identity] - Blockchain family: %s\n",
282 FAMILY_AS_STR(context->state->identity.blockchain_family));
283 if (context->state->identity.blockchain_family == FAMILY_ETHEREUM) {
284 PRINTF("[Register Identity] - Chain ID: %llu\n",
285 context->state->identity.chain_id);
286 }
287}
288
302static bool build_and_send_response(void)
303{
304 uint8_t group_handle[GROUP_HANDLE_SIZE] = {0};
305 uint8_t hmac_proof[CX_SHA256_SIZE] = {0};
306 uint8_t hmac_rest[CX_SHA256_SIZE] = {0};
307 bool ok = false;
308
309 if (g_ab_payload.reg.active) {
310 // group_handle and HMAC_PROOF were already verified before the UI — use the
311 // pre-extracted GID directly and only compute the new HMAC_REST.
312 if (!address_book_compute_hmac_rest(g_ab_payload.reg.gid,
313 g_ab_payload.reg.identity.scope,
314 g_ab_payload.reg.identity.identifier,
315 g_ab_payload.reg.identity.identifier_len,
316 g_ab_payload.reg.identity.blockchain_family,
317 g_ab_payload.reg.identity.chain_id,
318 hmac_rest)) {
319 PRINTF("[Register Identity] Error: Failed to compute HMAC_REST for new address\n");
320 goto end;
321 }
322 // Echo back the verified group_handle and hmac_proof; only hmac_rest is new
323 memmove(group_handle, g_ab_payload.reg.group_handle, GROUP_HANDLE_SIZE);
324 memmove(hmac_proof, g_ab_payload.reg.hmac_proof, CX_SHA256_SIZE);
325 }
326 else {
327 if (!address_book_generate_group_handle(group_handle)) {
328 PRINTF("[Register Identity] Error: Failed to generate group handle\n");
329 goto end;
330 }
331 // group_handle layout: gid(32) | MAC(K_group, gid)(32) — use only the GID prefix here
332 const uint8_t *gid = group_handle;
333 if (!address_book_compute_hmac_proof(
334 gid, g_ab_payload.reg.identity.contact_name, hmac_proof)) {
335 PRINTF("[Register Identity] Error: Failed to compute HMAC_PROOF\n");
336 goto end;
337 }
338 if (!address_book_compute_hmac_rest(gid,
339 g_ab_payload.reg.identity.scope,
340 g_ab_payload.reg.identity.identifier,
341 g_ab_payload.reg.identity.identifier_len,
342 g_ab_payload.reg.identity.blockchain_family,
343 g_ab_payload.reg.identity.chain_id,
344 hmac_rest)) {
345 PRINTF("[Register Identity] Error: Failed to compute HMAC_REST\n");
346 goto end;
347 }
348 }
349 ok = address_book_send_register_identity_response(group_handle, hmac_proof, hmac_rest);
350
351end:
352 explicit_bzero(group_handle, sizeof(group_handle));
353 explicit_bzero(hmac_proof, sizeof(hmac_proof));
354 explicit_bzero(hmac_rest, sizeof(hmac_rest));
355 return ok;
356}
357
363static void review_choice(bool confirm)
364{
365 if (confirm) {
366 bool ok = build_and_send_response();
367 if (!ok) {
368 PRINTF("[Register Identity] Error: Failed to build and send HMAC proof\n");
369 }
370 address_book_finalize_review(ok,
371 "Saved to your Contacts",
372 "Error during registration",
373 finalize_ui_register_identity);
374 }
375 else {
376 address_book_handle_review_rejected(finalize_ui_register_identity);
377 }
378}
379
383static void ui_display(const s_identity_ctx *context)
384{
385 memset(&g_ab_ui.list, 0, sizeof(g_ab_ui.list));
386 g_ab_ui.list.nbPairs = 3; // name + scope + identifier
387 if (context->state->identity.blockchain_family == FAMILY_ETHEREUM) {
388 g_ab_ui.list.nbPairs++; // network
389 }
390 g_ab_ui.list.callback = get_register_identity_tagValue;
391 address_book_display_review(&LARGE_ADDRESS_BOOK_ICON,
392 "Review contact details",
393#ifdef SCREEN_SIZE_WALLET
394 "Confirm contact details?",
395#else
396 "Confirm contact details",
397#endif
398 review_choice);
399}
400
401/* Exported functions --------------------------------------------------------*/
402
410bolos_err_t register_identity(uint8_t *buffer_in, size_t buffer_in_length)
411{
412 const buffer_t payload = {.ptr = buffer_in, .size = buffer_in_length};
413 s_identity_ctx ctx = {0};
414
415 // Init the structure
416 memset(&g_ab_payload.reg, 0, sizeof(g_ab_payload.reg));
417 ctx.state = &g_ab_payload.reg;
418
419 // Parse using SDK TLV parser — handlers write directly into g_ab_payload.reg via ctx.state
420 if (!identity_tlv_parser(&payload, &ctx, &ctx.received_tags)) {
421 PRINTF("[Register Identity] Failed to parse TLV payload\n");
422 return SWO_INCORRECT_DATA;
423 }
424 if (!verify_fields(&ctx)) {
425 return SWO_INCORRECT_DATA;
426 }
427 print_payload(&ctx);
428
429 // Validate group-handle integrity and caller ownership before showing the UI.
430 // These are input checks (wallet-supplied data) — failing here returns an error
431 // immediately; only internal crypto operations remain post-confirm.
432 if (TLV_CHECK_RECEIVED_TAGS(ctx.received_tags, TAG_GROUP_HANDLE)) {
433 g_ab_payload.reg.active = true;
434 if (!address_book_verify_group_handle(g_ab_payload.reg.group_handle,
435 g_ab_payload.reg.gid)) {
436 PRINTF("[Register Identity] Error: Group handle verification failed\n");
437 return SWO_SECURITY_CONDITION_NOT_SATISFIED;
438 }
439 if (!address_book_verify_hmac_proof(g_ab_payload.reg.gid,
440 g_ab_payload.reg.identity.contact_name,
441 g_ab_payload.reg.hmac_proof)) {
442 PRINTF("[Register Identity] Error: HMAC_PROOF verification failed\n");
443 return SWO_SECURITY_CONDITION_NOT_SATISFIED;
444 }
445 }
446
447 // Check the Identity validity according to the Coin application logic
448 if (!handle_check_register_identity(&g_ab_payload.reg.identity)) {
449 PRINTF("[Register Identity] Error: Identity rejected by coin application\n");
450 return SWO_WRONG_PARAMETER_VALUE;
451 }
452
453 // Display confirmation UI
454 ui_display(&ctx);
455 return SWO_NO_RESPONSE;
456}
457
458#endif // HAVE_ADDRESS_BOOK
@ FAMILY_ETHEREUM
#define FAMILY_AS_STR(x)
Register / Edit Contact Name / Edit Scope / Edit Identifier.
API of the Advanced BOLOS Graphical Library, for typical application use-cases.
uint8_t * ptr
Definition buffer.h:22
size_t size
Pointer to byte buffer.
Definition buffer.h:23
bool tlv_enforce_u8_value(const tlv_data_t *data, uint8_t expected_value)
Enforce that a TLV data contains the expected uint8 value.
Definition tlv_library.c:38
bool get_buffer_from_tlv_data(const tlv_data_t *data, buffer_t *out, uint16_t min_size, uint16_t max_size)
Extract a sized buffer from TLV data (zero-copy).
#define DEFINE_TLV_PARSER(TAG_LIST, COMMON_HANDLER, PARSE_FUNCTION_NAME)
Creates a parser function for a given TLV use case.
#define TLV_CHECK_RECEIVED_TAGS(received,...)
static bool handle_chain_id(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_blockchain_family(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)