Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New unified flows #140

Merged
merged 12 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions src/apdu_parser.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/*****************************************************************************
* Ledger
* (c) 2023 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*****************************************************************************/

#include "os.h"
#include "ux.h"
#include "os_io_seproxyhal.h"
#include "init.h"
#include "io.h"
#include "menu.h"
#include "globals.h"
#include "commands.h"
#include "command_dispatcher.h"
#include "apdu_offsets.h"
#include "swap_errors.h"
#include "apdu_parser.h"

// Keep as global the received fields of the command
// We need to remember them in case we receive a splitted command
typedef struct apdu_s {
bool expecting_more;
uint8_t instruction;
uint8_t rate;
uint8_t subcommand;
uint16_t data_length;
// We could have put the recontructed apdu buffer here but it would increase the RAM usage by 512 bytes
// which is a lot on NANOS. Instead the recontructed apdu buffer is G_swap_ctx.raw_transaction
// It is unionized with the decoded protobuf transaction requests
// Pro: less memory usage
// Cons: use cautiously and only during the command PROCESS_TRANSACTION_RESPONSE_COMMAND
// The split reception is only made for this command anyway
} apdu_t;
apdu_t G_received_apdu;

// Dedicated function for instruction checking as it's self contained
static uint16_t check_instruction(uint8_t instruction, uint8_t subcommand) {
bool check_subcommand_context = false;
bool allowed_during_waiting_for_signing = false;
int check_current_state = -1;

switch (instruction) {
case GET_VERSION_COMMAND:
// We don't care for the subcommand context for this command as it doesn't modify anything
check_subcommand_context = false;
// No strict dependancy on the current state as long as it is not a protected state
// (WAITING_USER_VALIDATION and WAITING_SIGNING)
check_current_state = -1;
break;
case START_NEW_TRANSACTION_COMMAND:
// We can always restart a new transaction
allowed_during_waiting_for_signing = true;
check_subcommand_context = false;
check_current_state = -1;
break;
case SET_PARTNER_KEY_COMMAND:
check_current_state = WAITING_TRANSACTION;
check_subcommand_context = true;
break;
case CHECK_PARTNER_COMMAND:
check_current_state = PROVIDER_SET;
check_subcommand_context = true;
break;
case PROCESS_TRANSACTION_RESPONSE_COMMAND:
check_current_state = PROVIDER_CHECKED;
check_subcommand_context = true;
break;
case CHECK_TRANSACTION_SIGNATURE_COMMAND:
check_current_state = TRANSACTION_RECEIVED;
check_subcommand_context = true;
break;
case CHECK_PAYOUT_ADDRESS:
check_current_state = SIGNATURE_CHECKED;
check_subcommand_context = true;
break;
case CHECK_REFUND_ADDRESS:
check_current_state = TO_ADDR_CHECKED;
check_subcommand_context = true;
break;
case START_SIGNING_TRANSACTION:
check_current_state = WAITING_SIGNING;
allowed_during_waiting_for_signing = true;
check_subcommand_context = true;
break;
default:
PRINTF("Received unknown subcommand %d\n", subcommand);
return INVALID_INSTRUCTION;
}

if (G_swap_ctx.state == WAITING_USER_VALIDATION) {
PRINTF("Refuse all APDUs during UI display\n");
return UNEXPECTED_INSTRUCTION;
}

if (!allowed_during_waiting_for_signing && G_swap_ctx.state == WAITING_SIGNING) {
PRINTF("Received subcommand %d, not allowed during WAITING_SIGNING state\n", subcommand);
return UNEXPECTED_INSTRUCTION;
}

if (check_subcommand_context && subcommand != G_swap_ctx.subcommand) {
PRINTF("Received subcommand %d, current flow is %d\n", subcommand, G_swap_ctx.subcommand);
return UNEXPECTED_INSTRUCTION;
}

if (check_current_state != -1 && G_swap_ctx.state != check_current_state) {
PRINTF("Received subcommand %d requiring state %d, but current state is %d\n",
subcommand,
check_current_state,
G_swap_ctx.state);
return UNEXPECTED_INSTRUCTION;
}

return 0;
}

// Return 0 if we can proceed with this APDU, return a status code if we can not
uint16_t apdu_parser(uint8_t *apdu, size_t apdu_length, command_t *command) {
if (apdu_length < OFFSET_CDATA) {
PRINTF("Error: malformed APDU, length is too short %d\n", apdu_length);
return MALFORMED_APDU;
}

uint8_t data_length = apdu[OFFSET_LC];
if (data_length != apdu_length - OFFSET_CDATA) {
PRINTF("Error: malformed APDU, received %d bytes APDU, including %d header bytes, claiming %d data bytes\n",
apdu_length,
OFFSET_CDATA,
data_length);
return MALFORMED_APDU;
}

if (apdu[OFFSET_CLA] != CLA) {
PRINTF("Error: invalid CLA %d\n", apdu[OFFSET_CLA]);
return CLASS_NOT_SUPPORTED;
}

uint8_t rate = apdu[OFFSET_P1];
if (rate != FIXED && rate != FLOATING) {
PRINTF("Incorrect P1 %d\n", rate);
return WRONG_P1;
}

uint8_t subcommand = apdu[OFFSET_P2] & SUBCOMMAND_MASK;
if (subcommand != SWAP
&& subcommand != SELL
&& subcommand != FUND
&& subcommand != SWAP_NG
&& subcommand != SELL_NG
&& subcommand != FUND_NG) {
PRINTF("Incorrect subcommand %d\n", subcommand);
return WRONG_P2_SUBCOMMAND;
}

uint8_t instruction = apdu[OFFSET_INS];
uint16_t err = check_instruction(instruction, subcommand);
if (err != 0) {
return err;
}

uint8_t extension = apdu[OFFSET_P2] & EXTENSION_MASK;
if ((extension & ~(P2_NONE|P2_MORE|P2_EXTEND)) != 0) {
PRINTF("Incorrect extension %d\n", extension);
return WRONG_P2_EXTENSION;
}

bool is_first_data_chunk = !(extension & P2_EXTEND);
bool is_last_data_chunk = !(extension & P2_MORE);
bool is_whole_apdu = is_first_data_chunk && is_last_data_chunk;
if (subcommand != SWAP_NG && subcommand != SELL_NG && subcommand != FUND_NG && !is_whole_apdu) {
PRINTF("Extension %d refused, only allowed for unified flows\n", extension);
return WRONG_P2_EXTENSION;
}
if (instruction != PROCESS_TRANSACTION_RESPONSE_COMMAND && !is_whole_apdu) {
PRINTF("Extension %d refused, only allowed for PROCESS_TRANSACTION_RESPONSE_COMMAND instruction\n", extension);
return WRONG_P2_EXTENSION;
}

if (is_first_data_chunk) {
G_received_apdu.instruction = instruction;
G_received_apdu.rate = rate;
G_received_apdu.subcommand = subcommand;
G_received_apdu.data_length = data_length;
if (!is_last_data_chunk) {
// Use the raw_transaction buffer as temporary storage
// It's unionized with the decoded transaction so we only use it for PROCESS_TRANSACTION_RESPONSE_COMMAND
// After this command is done it will contain decoded PB data, don't use it anymore
memcpy(G_swap_ctx.raw_transaction, apdu + OFFSET_CDATA, data_length);
G_received_apdu.expecting_more = true;
}
} else {
// The command received is supposed to extend the previously received one. Check if it's expected
if (!G_received_apdu.expecting_more) {
PRINTF("Previous command did not indicate a followup\n");
return INVALID_P2_EXTENSION;
}
// Also ensure they are the same kind
if (G_received_apdu.instruction != instruction
|| G_received_apdu.rate != rate
|| G_received_apdu.subcommand != subcommand) {
PRINTF("Refusing to extend a different apdu, expected (%d, %d, %d), received (%d, %d, %d)\n",
G_received_apdu.instruction,
G_received_apdu.rate,
G_received_apdu.subcommand,
instruction,
rate,
subcommand);
return INVALID_P2_EXTENSION;
}
if (G_received_apdu.data_length + data_length > sizeof(G_swap_ctx.raw_transaction)) {
PRINTF("Reception buffer size %d is not sufficient to receive more data (%d + %d)\n",
sizeof(G_swap_ctx.raw_transaction),
G_received_apdu.data_length,
data_length);
return INVALID_P2_EXTENSION;

}
// Extend the already received buffer
memcpy(G_swap_ctx.raw_transaction + G_received_apdu.data_length, apdu + OFFSET_CDATA, data_length);
G_received_apdu.data_length += data_length;
}

if (!is_last_data_chunk) {
// Reply a blank success to indicate that we await the followup part
// Do NOT update any kind of internal state machine, we have not validated what we have received
PRINTF("Split APDU successfully initiated, size %d\n", G_received_apdu.data_length);
return SUCCESS;
} else {
// The APDU is valid and complete, signal caller that it can proceed
command->ins = G_received_apdu.instruction;
command->rate = G_received_apdu.rate;
command->subcommand = G_received_apdu.subcommand;
command->data.size = G_received_apdu.data_length;
if (is_whole_apdu) {
// No split has taken place, data is still in the APDU reception buffer
command->data.bytes = apdu + OFFSET_CDATA;
} else {
// Split has taken place, data is in the split buffer
PRINTF("Split APDU successfully recreated, size %d\n", G_received_apdu.data_length);
command->data.bytes = G_swap_ctx.raw_transaction;
}
return 0;
}
}
5 changes: 5 additions & 0 deletions src/apdu_parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

#include "commands.h"

uint16_t apdu_parser(uint8_t *apdu, size_t apdu_length, command_t *command);
28 changes: 23 additions & 5 deletions src/buffer.c
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
#include "buffer.h"
#include "os.h"

// Parse a buffer at a given offset to read a buf_t, the offset is incremented accordingly
// param[in] <in_buffer> the total buffer to parse
// param[in] <in_size> the total size of the buffer to parse
// param[out] <out> the buf_t read from in_buffer at offset, can by 0 sized
// param[in/out] <offset> the current offset at wich we are in <in_buffer>
bool parse_to_sized_buffer(uint8_t *in_buffer, uint16_t in_size, buf_t *out, uint16_t *offset) {
if (*offset + 1 > in_size) {
bool parse_to_sized_buffer(uint8_t *in_buffer, uint16_t in_size, uint8_t size_of_lenght_field, buf_t *out, uint16_t *offset) {
if (*offset + size_of_lenght_field > in_size) {
// We can't even read the size
PRINTF("Failed to read the header sized %d, only %d bytes available\n",
size_of_lenght_field,
in_size);
return false;
}

// Read the size
out->size = in_buffer[*offset];
++*offset;
if (size_of_lenght_field == 1) {
out->size = in_buffer[*offset];
} else if (size_of_lenght_field == 2) {
out->size = U2BE(in_buffer, *offset);
} else if (size_of_lenght_field == 4) {
out->size = U4BE(in_buffer, *offset);
} else {
PRINTF("Unable to read a %d sized header\n", size_of_lenght_field);
return false;
}
*offset += size_of_lenght_field;


if (*offset + out->size > in_size) {
// Not enough bytes to read buffer size
PRINTF("Not enough remaining bytes to read. Total bytes %d, offset %d, hedaer advertizes %d bytes\n",
in_size,
*offset,
out->size);
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion src/buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ typedef struct buf_s {
uint16_t size;
} buf_t;

bool parse_to_sized_buffer(uint8_t *in_buffer, uint16_t in_size, buf_t *out, uint16_t *offset);
bool parse_to_sized_buffer(uint8_t *in_buffer, uint16_t in_size, uint8_t size_of_lenght_field, buf_t *out, uint16_t *offset);
Loading