Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
tlv_use_case_transaction_check.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
17#include <stdint.h>
18#include <string.h>
19
20#include "os_utils.h"
21#include "ox_ec.h"
22#include "cx.h"
23#include "os_pki.h"
24#include "ledger_pki.h"
26
27#define TYPE_TRANSACTION_CHECK 0x09
28#define TRANSACTION_CHECK_VERSION 0x01
29
30// Parsed TLV data (internal context)
31typedef struct tlv_extracted_s {
32 // Received tags set by the parser
34
35 // Transaction Check output data
37
38 // Tags handled by the use case API and not the caller
39 uint8_t structure_type;
41
43
44 // Progressive hash of the received TLVs (except the signature tag)
45 cx_sha256_t hash_ctx;
47
48// ─────────────────────────────────────────────────────────────────────────────
49// Tag handlers
50// ─────────────────────────────────────────────────────────────────────────────
51
52static bool handle_structure_type(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
53{
54 return get_uint8_t_from_tlv_data(data, &tlv_extracted->structure_type);
55}
56
57static bool handle_structure_version(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
58{
59 return get_uint8_t_from_tlv_data(data, &tlv_extracted->structure_version);
60}
61
62static bool handle_tx_hash(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
63{
64 return get_buffer_from_tlv_data(data,
65 &tlv_extracted->output->tx_hash,
68}
69
70static bool handle_domain_hash(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
71{
72 return get_buffer_from_tlv_data(data,
73 &tlv_extracted->output->domain_hash,
76}
77
78static bool handle_address(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
79{
80 return get_buffer_from_tlv_data(data,
81 &tlv_extracted->output->address,
84}
85
86static bool handle_chain_id(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
87{
88 return get_uint64_t_from_tlv_data(data, &tlv_extracted->output->chain_id);
89}
90
91static bool handle_risk(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
92{
93 uint8_t value = 0;
94 if (!get_uint8_t_from_tlv_data(data, &value)) {
95 PRINTF("TX_CHECK_NORMALIZED_RISK: failed to extract\n");
96 return false;
97 }
98 if (value >= TRANSACTION_CHECK_RISK_COUNT) {
99 PRINTF("TX_CHECK_NORMALIZED_RISK: value %d out of range\n", value);
100 return false;
101 }
102 tlv_extracted->output->risk = (transaction_check_risk_t) value;
103 return true;
104}
105
106static bool handle_category(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
107{
108 uint8_t value = 0;
109 if (!get_uint8_t_from_tlv_data(data, &value)) {
110 PRINTF("TX_CHECK_NORMALIZED_CATEGORY: failed to extract\n");
111 return false;
112 }
114 PRINTF("TX_CHECK_NORMALIZED_CATEGORY: value %d out of range\n", value);
115 return false;
116 }
117 tlv_extracted->output->category = (transaction_check_category_t) value;
118 return true;
119}
120
121static bool handle_type(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
122{
123 uint8_t value = 0;
124 if (!get_uint8_t_from_tlv_data(data, &value)) {
125 PRINTF("TX_CHECK_SIMULATION_TYPE: failed to extract\n");
126 return false;
127 }
128 if (value >= TRANSACTION_CHECK_TYPE_COUNT) {
129 PRINTF("TX_CHECK_SIMULATION_TYPE: value %d out of range\n", value);
130 return false;
131 }
132 tlv_extracted->output->type = (transaction_check_type_t) value;
133 return true;
134}
135
136static bool handle_provider_msg(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
137{
138 if (!get_string_from_tlv_data(data,
139 tlv_extracted->output->provider_msg,
140 0,
141 sizeof(tlv_extracted->output->provider_msg))) {
142 PRINTF("TX_CHECK_PROVIDER_MESSAGE: failed to extract\n");
143 return false;
144 }
145 if (!is_printable_string(tlv_extracted->output->provider_msg,
146 strlen(tlv_extracted->output->provider_msg))) {
147 PRINTF("TX_CHECK_PROVIDER_MESSAGE: not printable\n");
148 return false;
149 }
150 return true;
151}
152
153static bool handle_tiny_url(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
154{
156 data, tlv_extracted->output->tiny_url, 0, sizeof(tlv_extracted->output->tiny_url))) {
157 PRINTF("TX_CHECK_TINY_URL: failed to extract\n");
158 return false;
159 }
160 if (!is_printable_string(tlv_extracted->output->tiny_url,
161 strlen(tlv_extracted->output->tiny_url))) {
162 PRINTF("TX_CHECK_TINY_URL: not printable\n");
163 return false;
164 }
165 return true;
166}
167
168static bool handle_signature(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
169{
170 return get_buffer_from_tlv_data(data,
171 &tlv_extracted->input_sig,
172 CX_ECDSA_SHA256_SIG_MIN_ASN1_LENGTH,
173 CX_ECDSA_SHA256_SIG_MAX_ASN1_LENGTH);
174}
175
176// ─────────────────────────────────────────────────────────────────────────────
177// TLV parser macro
178// ─────────────────────────────────────────────────────────────────────────────
179
180static bool handle_common(const tlv_data_t *data, tlv_extracted_t *tlv_extracted);
181
182// clang-format off
183#define TRANSACTION_CHECK_TLV_TAGS(X) \
184 X(0x01, TAG_STRUCTURE_TYPE, handle_structure_type, ENFORCE_UNIQUE_TAG) \
185 X(0x02, TAG_STRUCTURE_VERSION, handle_structure_version, ENFORCE_UNIQUE_TAG) \
186 X(0x22, TAG_ADDRESS, handle_address, ENFORCE_UNIQUE_TAG) \
187 X(0x23, TAG_CHAIN_ID, handle_chain_id, ENFORCE_UNIQUE_TAG) \
188 X(0x27, TAG_TX_HASH, handle_tx_hash, ENFORCE_UNIQUE_TAG) \
189 X(0x28, TAG_DOMAIN_HASH, handle_domain_hash, ENFORCE_UNIQUE_TAG) \
190 X(0x80, TAG_NORMALIZED_RISK, handle_risk, ENFORCE_UNIQUE_TAG) \
191 X(0x81, TAG_NORMALIZED_CATEGORY, handle_category, ENFORCE_UNIQUE_TAG) \
192 X(0x82, TAG_PROVIDER_MSG, handle_provider_msg, ENFORCE_UNIQUE_TAG) \
193 X(0x83, TAG_TINY_URL, handle_tiny_url, ENFORCE_UNIQUE_TAG) \
194 X(0x84, TAG_SIMULATION_TYPE, handle_type, ENFORCE_UNIQUE_TAG) \
195 X(0x85, TAG_ADDITIONAL_DATA, NULL, ENFORCE_UNIQUE_TAG) \
196 X(0x15, TAG_DER_SIGNATURE, handle_signature, ENFORCE_UNIQUE_TAG)
197// clang-format on
198
199DEFINE_TLV_PARSER(TRANSACTION_CHECK_TLV_TAGS, &handle_common, parse_transaction_check_tlv)
200
201
204static bool handle_common(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
205{
206 if (data->tag != TAG_DER_SIGNATURE) {
207 CX_ASSERT(
208 cx_hash_update((cx_hash_t *) &tlv_extracted->hash_ctx, data->raw.ptr, data->raw.size));
209 }
210 return true;
211}
212
213// ─────────────────────────────────────────────────────────────────────────────
214// Verification
215// ─────────────────────────────────────────────────────────────────────────────
216
218{
219 // Check structure type tag present
220 if (!TLV_CHECK_RECEIVED_TAGS(tlv_extracted->received_tags, TAG_STRUCTURE_TYPE)) {
221 PRINTF("Error: no struct type specified!\n");
223 }
224
225 if (tlv_extracted->structure_type != TYPE_TRANSACTION_CHECK) {
226 PRINTF("Error: unexpected struct type %d\n", tlv_extracted->structure_type);
228 }
229
230 // Check mandatory fields
231 if (!TLV_CHECK_RECEIVED_TAGS(tlv_extracted->received_tags,
232 TAG_STRUCTURE_VERSION,
233 TAG_TX_HASH,
234 TAG_ADDRESS,
235 TAG_NORMALIZED_RISK,
236 TAG_NORMALIZED_CATEGORY,
237 TAG_TINY_URL,
238 TAG_SIMULATION_TYPE,
239 TAG_DER_SIGNATURE)) {
240 PRINTF("Error: missing mandatory tags in Transaction Check TLV\n");
242 }
243
244 // Version check
245 if (tlv_extracted->structure_version != TRANSACTION_CHECK_VERSION) {
246 PRINTF("Error: unsupported Transaction Check version %d\n",
247 tlv_extracted->structure_version);
249 }
250
251 // Forward optional field reception flags
252 tlv_extracted->output->chain_id_received
253 = TLV_CHECK_RECEIVED_TAGS(tlv_extracted->received_tags, TAG_CHAIN_ID);
254 tlv_extracted->output->domain_hash_received
255 = TLV_CHECK_RECEIVED_TAGS(tlv_extracted->received_tags, TAG_DOMAIN_HASH);
256 tlv_extracted->output->provider_msg_received
257 = TLV_CHECK_RECEIVED_TAGS(tlv_extracted->received_tags, TAG_PROVIDER_MSG);
258 tlv_extracted->output->additional_data_received
259 = TLV_CHECK_RECEIVED_TAGS(tlv_extracted->received_tags, TAG_ADDITIONAL_DATA);
260
262}
263
265{
266 // Finalize the SHA-256 hash
267 uint8_t tlv_hash[CX_SHA256_SIZE] = {0};
268 CX_ASSERT(cx_hash_final((cx_hash_t *) &tlv_extracted->hash_ctx, tlv_hash));
269 buffer_t hash = {.ptr = tlv_hash, .size = sizeof(tlv_hash)};
270
271 // Verify with PKI
272 uint8_t expected_key_usage = CERTIFICATE_PUBLIC_KEY_USAGE_TX_SIMU_SIGNER;
273 cx_curve_t expected_curve = CX_CURVE_SECP256K1;
274
277 hash, &expected_key_usage, &expected_curve, tlv_extracted->input_sig);
279 PRINTF("Failed to verify Transaction Check signature\n");
281 }
282
284}
285
286// ─────────────────────────────────────────────────────────────────────────────
287// Public API
288// ─────────────────────────────────────────────────────────────────────────────
289
292{
293 // Initialize the parsing context
294 tlv_extracted_t tlv_extracted = {0};
295 tlv_extracted.output = out;
296
297 // Reset output
298 memset(out, 0, sizeof(*out));
299
300 // Initialize the progressive SHA-256 hash
301 CX_ASSERT(cx_sha256_init_no_throw(&tlv_extracted.hash_ctx));
302
303 // Parse the TLV payload
304 if (!parse_transaction_check_tlv(payload, &tlv_extracted, &tlv_extracted.received_tags)) {
305 PRINTF("Failed to parse Transaction Check TLV payload\n");
307 }
308
309 // Verify structural integrity
310 tlv_transaction_check_status_t err = verify_struct(&tlv_extracted);
312 PRINTF("Failed to verify Transaction Check TLV structure\n");
313 return err;
314 }
315
316 // Verify PKI signature
317 err = verify_signature(&tlv_extracted);
319 PRINTF("Failed to verify Transaction Check signature\n");
320 return err;
321 }
322
323 // Retrieve partner name from the PKI certificate
324 uint8_t key_usage = 0;
325 size_t trusted_name_len = 0;
326 uint8_t trusted_name[CERTIFICATE_TRUSTED_NAME_MAXLEN] = {0};
327 cx_ecfp_384_public_key_t public_key = {0};
328 if (os_pki_get_info(&key_usage, trusted_name, &trusted_name_len, &public_key) == 0) {
329 PRINTF("Using certificate name as partner name\n");
330 strlcpy(out->partner, (char *) trusted_name, sizeof(out->partner));
331 }
332 // Set a default value for partner
333 if (strlen(out->partner) == 0) {
334 PRINTF("Using default name as partner name\n");
335 snprintf(out->partner, sizeof(out->partner), "Transaction Checks");
336 }
337
339}
check_signature_with_pki_status_t check_signature_with_pki(const buffer_t hash, const uint8_t *expected_key_usage, const cx_curve_t *expected_curve, const buffer_t signature)
Checks a signature using the PKI certificate.
Definition ledger_pki.c:5
@ CHECK_SIGNATURE_WITH_PKI_SUCCESS
Definition ledger_pki.h:7
enum check_signature_with_pki_status_e check_signature_with_pki_status_t
uint8_t * ptr
Definition buffer.h:22
tlv_dynamic_descriptor_out_t * output
tlv_transaction_check_out_t * output
char partner[TRANSACTION_CHECK_PARTNER_SIZE+1]
bool get_string_from_tlv_data(const tlv_data_t *data, char *out, uint16_t min_length, uint16_t out_size)
Copy a NUL-terminated string from TLV data.
bool get_uint64_t_from_tlv_data(const tlv_data_t *data, uint64_t *value)
Extract a uint64_t value from TLV data.
Definition tlv_library.c:64
bool get_uint8_t_from_tlv_data(const tlv_data_t *data, uint8_t *value)
Extract a uint8_t value from TLV data.
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_signature(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_tx_hash(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_tiny_url(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_domain_hash(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_chain_id(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_category(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
struct tlv_extracted_s tlv_extracted_t
static bool handle_address(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
#define TRANSACTION_CHECK_TLV_TAGS(X)
static tlv_transaction_check_status_t verify_signature(tlv_extracted_t *tlv_extracted)
tlv_transaction_check_status_t tlv_use_case_transaction_check(const buffer_t *payload, tlv_transaction_check_out_t *out)
Parse a Transaction Check TLV payload.
static bool handle_provider_msg(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
#define TRANSACTION_CHECK_VERSION
static tlv_transaction_check_status_t verify_struct(const tlv_extracted_t *tlv_extracted)
static bool handle_structure_version(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
#define TYPE_TRANSACTION_CHECK
static bool handle_type(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_structure_type(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_risk(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_common(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
Common handler called for all tags to progressively hash them (except signature).
#define TRANSACTION_CHECK_HASH_SIZE
#define TRANSACTION_CHECK_MIN_ADDRESS_SIZE
enum transaction_check_risk_e transaction_check_risk_t
#define TRANSACTION_CHECK_MAX_ADDRESS_SIZE
enum tlv_transaction_check_status_e tlv_transaction_check_status_t
enum transaction_check_type_e transaction_check_type_t
@ TRANSACTION_CHECK_CATEGORY_COUNT
@ TLV_TRANSACTION_CHECK_SIGNATURE_ERROR
@ TLV_TRANSACTION_CHECK_UNKNOWN_VERSION
@ TLV_TRANSACTION_CHECK_MISSING_STRUCTURE_TAG
@ TLV_TRANSACTION_CHECK_MISSING_TAG
@ TLV_TRANSACTION_CHECK_PARSING_ERROR
enum transaction_check_category_e transaction_check_category_t