-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #140 from LedgerHQ/fbe/unified_flows
New unified flows
- Loading branch information
Showing
40 changed files
with
1,256 additions
and
882 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
/***************************************************************************** | ||
* 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 split 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 ignore the current 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, recv %d bytes, claiming %d header bytes and %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; | ||
} | ||
|
||
// Get rate from P1 | ||
uint8_t rate = apdu[OFFSET_P1]; | ||
if (rate != FIXED && rate != FLOATING) { | ||
PRINTF("Incorrect P1 %d\n", rate); | ||
return WRONG_P1; | ||
} | ||
|
||
// Extract subcommand from P2 | ||
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; | ||
} | ||
|
||
// Get instruction and ensure it makes sense in the current context | ||
uint8_t instruction = apdu[OFFSET_INS]; | ||
uint16_t err = check_instruction(instruction, subcommand); | ||
if (err != 0) { | ||
return err; | ||
} | ||
|
||
// Extract extension from P2 | ||
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; | ||
// Split reception is only for NG flows | ||
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; | ||
} | ||
// Split reception is only for PROCESS_TRANSACTION_RESPONSE_COMMAND | ||
if (instruction != PROCESS_TRANSACTION_RESPONSE_COMMAND && !is_whole_apdu) { | ||
PRINTF("Extension %d refused, only allowed for PROCESS_TRANSACTION_RESPONSE 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 but we are currently handling | ||
// PROCESS_TRANSACTION_RESPONSE_COMMAND instruction (checked previously). | ||
// After this command is done it will contain the 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 claims to extend a 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, exp (%d, %d, %d), recv (%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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.