LCOV - code coverage report
Current view: top level - ledger/ui/src - attestation.c (source / functions) Hit Total Coverage
Test: powHSM firmware Lines: 74 86 86.0 %
Date: 2025-07-10 13:49:13 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /**
       2             :  * The MIT License (MIT)
       3             :  *
       4             :  * Copyright (c) 2021 RSK Labs Ltd
       5             :  *
       6             :  * Permission is hereby granted, free of charge, to any person obtaining a copy
       7             :  * of this software and associated documentation files (the "Software"), to
       8             :  * deal in the Software without restriction, including without limitation the
       9             :  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
      10             :  * sell copies of the Software, and to permit persons to whom the Software is
      11             :  * furnished to do so, subject to the following conditions:
      12             :  *
      13             :  * The above copyright notice and this permission notice shall be included in
      14             :  * all copies or substantial portions of the Software.
      15             :  *
      16             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      17             :  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      18             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      19             :  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      20             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      21             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
      22             :  * IN THE SOFTWARE.
      23             :  */
      24             : 
      25             : #include <string.h>
      26             : #include "os.h"
      27             : #include "cx.h"
      28             : #include "attestation.h"
      29             : #include "defs.h"
      30             : #include "ui_err.h"
      31             : #include "memutil.h"
      32             : #include "ints.h"
      33             : #include "runtime.h"
      34             : 
      35             : // Utility macros to save memory
      36             : #define MIN(x, y) ((x) < (y) ? (x) : (y))
      37             : #define PAGESIZE (APDU_TOTAL_DATA_SIZE_OUT - 1)
      38             : #define PAGECOUNT(itemcount) (((itemcount) + PAGESIZE - 1) / PAGESIZE)
      39             : 
      40             : // Global onboarding flag
      41             : extern NON_VOLATILE unsigned char* N_onboarded_ui[1];
      42             : 
      43             : // Attestation message prefix
      44             : const char att_msg_prefix[ATT_MSG_PREFIX_LENGTH] = ATT_MSG_PREFIX;
      45             : 
      46             : // Path of the public key to derive
      47             : const char key_derivation_path[PUBKEY_PATH_LENGTH] = PUBKEY_PATH;
      48             : 
      49             : /*
      50             :  * Check the SM for the attestation generation
      51             :  * matches the expected state.
      52             :  *
      53             :  * Reset the state and throw a protocol error
      54             :  * otherwise.
      55             :  */
      56           7 : static void check_state(att_t* att_ctx, att_state_t expected) {
      57           7 :     if (att_ctx->state != expected) {
      58           3 :         reset_attestation(att_ctx);
      59           3 :         THROW(ERR_UI_PROT_INVALID);
      60             :     }
      61           4 : }
      62             : 
      63             : /*
      64             :  * Throw an internal error unless the APDU
      65             :  * buffer data part is large enough to fit
      66             :  * the given number of bytes
      67             :  *
      68             :  * The reason this is treated as an internal
      69             :  * error is that all operations checking this
      70             :  * already know that the buffer *should* be
      71             :  * large enough and are just sanity checking
      72             :  */
      73           1 : static void check_apdu_buffer_holds(size_t size) {
      74           1 :     if (APDU_TOTAL_DATA_SIZE_OUT < size) {
      75           0 :         THROW(ERR_UI_INTERNAL);
      76             :     }
      77           1 : }
      78             : 
      79             : /*
      80             :  * Given an uncompressed secp256k1 public key,
      81             :  * compress it to the given destination, returning
      82             :  * the size.
      83             :  *
      84             :  * @arg[in] pub_key public key
      85             :  * @arg[in] dst destination
      86             :  * @arg[in] dst_size destination size
      87             :  * @arg[in] dst_offset destination offset
      88             :  * @ret     size of the compressed public key
      89             :  */
      90           1 : static size_t compress_pubkey_into(cx_ecfp_public_key_t* pub_key,
      91             :                                    uint8_t* dst,
      92             :                                    size_t dst_size,
      93             :                                    size_t dst_offset) {
      94           2 :     SAFE_MEMMOVE(dst,
      95             :                  dst_size,
      96             :                  dst_offset,
      97             :                  pub_key->W,
      98             :                  sizeof(pub_key->W),
      99             :                  MEMMOVE_ZERO_OFFSET,
     100             :                  PUBKEY_CMP_LENGTH,
     101             :                  THROW(ERR_UI_INTERNAL));
     102           1 :     dst[dst_offset] = pub_key->W[pub_key->W_len - 1] & 0x01 ? 0x03 : 0x02;
     103           1 :     return PUBKEY_CMP_LENGTH;
     104             : }
     105             : 
     106             : /*
     107             :  * Reset the given attestation context
     108             :  *
     109             :  * @arg[in] att_ctx attestation context
     110             :  */
     111          12 : void reset_attestation(att_t* att_ctx) {
     112          12 :     explicit_bzero(att_ctx, sizeof(att_t));
     113          12 :     att_ctx->state = att_state_wait_ud_value;
     114          12 : }
     115             : 
     116             : // -----------------------------------------------------------------------
     117             : // Protocol implementation
     118             : // -----------------------------------------------------------------------
     119             : 
     120             : /*
     121             :  * Implement the attestation protocol.
     122             :  *
     123             :  * @arg[in] rx      number of received bytes from the Host
     124             :  * @arg[in] att_ctx attestation context
     125             :  * @ret             number of transmited bytes to the host
     126             :  */
     127           9 : unsigned int get_attestation(volatile unsigned int rx, att_t* att_ctx) {
     128             :     uint8_t message_size;
     129             : 
     130             :     // Verify that the device has been onboarded
     131           9 :     unsigned char onboarded = *((unsigned char*)PIC(N_onboarded_ui));
     132           9 :     if (!onboarded) {
     133           1 :         THROW(ATT_NO_ONBOARD);
     134             :         return 0;
     135             :     }
     136             : 
     137           8 :     switch (APDU_OP()) {
     138           2 :     case OP_ATT_UD_VALUE:
     139           2 :         check_state(att_ctx, att_state_wait_ud_value);
     140             : 
     141             :         // Should receive a user-defined value
     142           1 :         if (APDU_DATA_SIZE(rx) != UD_VALUE_SIZE)
     143           0 :             THROW(ERR_UI_PROT_INVALID);
     144             : 
     145           1 :         sigaut_signer_t* current_signer_info = get_authorized_signer_info();
     146             : 
     147             :         // Initialize message
     148           1 :         explicit_bzero(att_ctx->msg, sizeof(att_ctx->msg));
     149           1 :         att_ctx->msg_offset = 0;
     150             : 
     151             :         // Copy prefix and user defined value into the message space
     152           2 :         SAFE_MEMMOVE(att_ctx->msg,
     153             :                      sizeof(att_ctx->msg),
     154             :                      att_ctx->msg_offset,
     155             :                      (const void*)PIC(att_msg_prefix),
     156             :                      sizeof(att_msg_prefix),
     157             :                      MEMMOVE_ZERO_OFFSET,
     158             :                      sizeof(att_msg_prefix),
     159             :                      THROW(ERR_UI_INTERNAL));
     160           1 :         att_ctx->msg_offset += sizeof(att_msg_prefix);
     161           2 :         SAFE_MEMMOVE(att_ctx->msg,
     162             :                      sizeof(att_ctx->msg),
     163             :                      att_ctx->msg_offset,
     164             :                      APDU_DATA_PTR,
     165             :                      APDU_TOTAL_DATA_SIZE,
     166             :                      MEMMOVE_ZERO_OFFSET,
     167             :                      UD_VALUE_SIZE,
     168             :                      THROW(ERR_UI_INTERNAL));
     169           1 :         att_ctx->msg_offset += UD_VALUE_SIZE;
     170             : 
     171             :         // Derive, compress and copy the public key into the message space
     172             :         BEGIN_TRY {
     173           1 :             TRY {
     174           2 :                 SAFE_MEMMOVE(att_ctx->path,
     175             :                              sizeof(att_ctx->path),
     176             :                              MEMMOVE_ZERO_OFFSET,
     177             :                              (const void*)PIC(key_derivation_path),
     178             :                              sizeof(key_derivation_path),
     179             :                              MEMMOVE_ZERO_OFFSET,
     180             :                              sizeof(key_derivation_path),
     181             :                              THROW(ERR_UI_INTERNAL));
     182             :                 // Derive private key
     183           1 :                 os_perso_derive_node_bip32(CX_CURVE_256K1,
     184           1 :                                            (unsigned int*)att_ctx->path,
     185             :                                            PATH_PART_COUNT,
     186           1 :                                            att_ctx->priv_key_data,
     187             :                                            NULL);
     188           1 :                 cx_ecdsa_init_private_key(CX_CURVE_256K1,
     189           1 :                                           att_ctx->priv_key_data,
     190             :                                           PRIVATE_KEY_LENGTH,
     191             :                                           &att_ctx->priv_key);
     192             :                 // Cleanup private key data
     193           1 :                 explicit_bzero(att_ctx->priv_key_data,
     194             :                                sizeof(att_ctx->priv_key_data));
     195             :                 // Derive public key
     196           1 :                 cx_ecfp_generate_pair(
     197             :                     CX_CURVE_256K1, &att_ctx->pub_key, &att_ctx->priv_key, 1);
     198             :                 // Cleanup private key
     199           1 :                 explicit_bzero(&att_ctx->priv_key, sizeof(att_ctx->priv_key));
     200             :                 // Compress public key into message space
     201           1 :                 att_ctx->msg_offset +=
     202           2 :                     compress_pubkey_into(&att_ctx->pub_key,
     203           1 :                                          att_ctx->msg,
     204             :                                          sizeof(att_ctx->msg),
     205           1 :                                          att_ctx->msg_offset);
     206             :                 // Cleanup public key
     207           1 :                 explicit_bzero(&att_ctx->pub_key, sizeof(att_ctx->pub_key));
     208             :             }
     209           1 :             CATCH_OTHER(e) {
     210             :                 // Cleanup key data and fail
     211           0 :                 explicit_bzero(att_ctx->priv_key_data,
     212             :                                sizeof(att_ctx->priv_key_data));
     213           0 :                 explicit_bzero(&att_ctx->priv_key, sizeof(att_ctx->priv_key));
     214           0 :                 explicit_bzero(&att_ctx->pub_key, sizeof(att_ctx->pub_key));
     215           0 :                 THROW(ERR_UI_INTERNAL);
     216             :             }
     217           1 :             FINALLY {
     218             :             }
     219             :         }
     220           1 :         END_TRY;
     221             : 
     222             :         // Copy signer hash and iteration into the message space
     223           2 :         SAFE_MEMMOVE(att_ctx->msg,
     224             :                      sizeof(att_ctx->msg),
     225             :                      att_ctx->msg_offset,
     226             :                      current_signer_info->hash,
     227             :                      sizeof(current_signer_info->hash),
     228             :                      MEMMOVE_ZERO_OFFSET,
     229             :                      sizeof(current_signer_info->hash),
     230             :                      THROW(ERR_UI_INTERNAL));
     231           1 :         att_ctx->msg_offset += sizeof(current_signer_info->hash);
     232             : 
     233             :         // Make sure iteration fits
     234           1 :         if (att_ctx->msg_offset + sizeof(current_signer_info->iteration) >
     235             :             sizeof(att_ctx->msg))
     236           0 :             THROW(ERR_UI_INTERNAL);
     237             : 
     238           3 :         VAR_BIGENDIAN_TO(att_ctx->msg + att_ctx->msg_offset,
     239             :                          current_signer_info->iteration,
     240             :                          sizeof(current_signer_info->iteration));
     241           1 :         att_ctx->msg_offset += sizeof(current_signer_info->iteration);
     242             : 
     243           1 :         att_ctx->state = att_state_ready;
     244             : 
     245           1 :         return TX_FOR_DATA_SIZE(0);
     246           3 :     case OP_ATT_GET_MSG:
     247             :         // Retrieve message to sign page
     248           3 :         check_state(att_ctx, att_state_ready);
     249             : 
     250             :         // Should receive a page index
     251           2 :         if (APDU_DATA_SIZE(rx) != 1)
     252           0 :             THROW(ERR_UI_PROT_INVALID);
     253             : 
     254             :         // Maximum page size is APDU data part size minus one
     255             :         // byte (first byte of the response), which is used to indicate
     256             :         // whether there is a next page or not.
     257             : 
     258             :         // Check page index within range (page index is zero based)
     259           2 :         if (APDU_DATA_PTR[0] >= PAGECOUNT(att_ctx->msg_offset)) {
     260           0 :             THROW(ERR_UI_PROT_INVALID);
     261             :         }
     262             : 
     263             :         // Copy the page into the APDU buffer (no need to check for limits since
     264             :         // the chunk size is based directly on the APDU size)
     265           2 :         message_size =
     266           2 :             MIN(PAGESIZE, att_ctx->msg_offset - (APDU_DATA_PTR[0] * PAGESIZE));
     267           4 :         SAFE_MEMMOVE(APDU_DATA_PTR,
     268             :                      APDU_TOTAL_DATA_SIZE_OUT,
     269             :                      1,
     270             :                      att_ctx->msg,
     271             :                      sizeof(att_ctx->msg),
     272             :                      APDU_DATA_PTR[0] * PAGESIZE,
     273             :                      message_size,
     274             :                      THROW(ERR_UI_INTERNAL));
     275           2 :         APDU_DATA_PTR[0] =
     276           2 :             APDU_DATA_PTR[0] < (PAGECOUNT(att_ctx->msg_offset) - 1);
     277             : 
     278           2 :         return TX_FOR_DATA_SIZE(message_size + 1);
     279           2 :     case OP_ATT_GET:
     280           2 :         check_state(att_ctx, att_state_ready);
     281             : 
     282             :         // Reset SM
     283           1 :         att_ctx->state = att_state_wait_ud_value;
     284             : 
     285             :         // Sign and output
     286           1 :         check_apdu_buffer_holds(MAX_SIGNATURE_LENGTH);
     287           1 :         return TX_FOR_DATA_SIZE(os_endorsement_key2_derive_sign_data(
     288             :             att_ctx->msg, att_ctx->msg_offset, APDU_DATA_PTR));
     289           0 :     case OP_ATT_APP_HASH:
     290             :         // This can be asked for at any time
     291           0 :         check_apdu_buffer_holds(HASH_LENGTH);
     292           0 :         return TX_FOR_DATA_SIZE(os_endorsement_get_code_hash(APDU_DATA_PTR));
     293           1 :     default:
     294             :         // Reset SM
     295           1 :         att_ctx->state = att_state_wait_ud_value;
     296           1 :         THROW(ERR_UI_PROT_INVALID);
     297             :         break;
     298             :     }
     299             : }

Generated by: LCOV version 1.16