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

Prevent re-onboarding #115

Merged
merged 1 commit into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions ledger/src/ui/src/bootloader.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ static unsigned char current_cmd;
// Flag used to prevent executing commands after the onboard is performed
static bool onboard_performed = false;

// Macro that throws an error unless
// the device is not onboarded
#define REQUIRE_NOT_ONBOARDED() \
if (os_perso_isonboarded() == 1) \
THROW(ERR_DEVICE_ONBOARDED);

/*
* Reset all reseteable operations, only if the given operation is starting.
*
Expand Down Expand Up @@ -96,6 +102,7 @@ unsigned int bootloader_process_apdu(volatile unsigned int rx,
// unauthenticated instruction
switch (APDU_CMD()) {
case RSK_SEED_CMD: // Send wordlist
REQUIRE_NOT_ONBOARDED();
reset_if_starting(RSK_META_CMD_UIOP);
tx = set_host_seed(rx, &onboard_ctx);
break;
Expand All @@ -108,6 +115,7 @@ unsigned int bootloader_process_apdu(volatile unsigned int rx,
tx = is_onboarded();
break;
case RSK_WIPE: //--- wipe and onboard device ---
REQUIRE_NOT_ONBOARDED();
reset_if_starting(RSK_META_CMD_UIOP);
tx = onboard_device(&onboard_ctx);
clear_pin();
Expand Down
1 change: 1 addition & 0 deletions ledger/src/ui/src/err.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ typedef enum {
ERR_INVALID_CLA = 0x6E22,
ERR_INTERNAL = 0x6A99,
ERR_INVALID_PIN = 0x69A0,
ERR_DEVICE_ONBOARDED = 0x69A1,

EX_BOOTLOADER_RSK_END = 0x90FF,
} err_code_ui_t;
Expand Down
107 changes: 107 additions & 0 deletions ledger/src/ui/test/bootloader/test_bootloader.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ bolos_ux_context_t G_bolos_ux_context;
static bootloader_mode_t G_bootloader_mode = BOOTLOADER_MODE_DEFAULT;
static bool G_host_seed_is_set = false;
static bool G_pin_buffer_updated = false;
static unsigned int G_device_onboarded = 0;
static bool G_is_onboarded = false;
static bool G_is_pin_set = false;
static bool G_is_pin_buffer_cleared = false;
Expand All @@ -62,6 +63,7 @@ static void reset_flags() {
G_host_seed_is_set = false;
G_pin_buffer_updated = false;
G_is_onboarded = false;
G_device_onboarded = 0;
G_is_pin_set = false;
G_is_pin_buffer_cleared = false;
G_get_attestation_called = false;
Expand All @@ -88,6 +90,10 @@ unsigned int update_pin_buffer(volatile unsigned int rx) {
return 3;
}

unsigned int os_perso_isonboarded(void) {
return G_device_onboarded;
}

unsigned int is_onboarded() {
G_is_onboarded_called = true;
SET_APDU_AT(1, G_is_onboarded);
Expand Down Expand Up @@ -195,6 +201,36 @@ void test_seed() {
assert(RESET_IF_STARTED_CALLED());
}

void test_seed_onboarded() {
printf("Test RSK_SEED_CMD when onboarded...\n");

unsigned int rx;
unsigned int tx;
bootloader_init();
reset_flags();
G_bootloader_mode = BOOTLOADER_MODE_DEFAULT;
G_device_onboarded = 1;
G_host_seed_is_set = false;
SET_APDU("\x80\x44", rx); // RSK_SEED_CMD
BEGIN_TRY {
TRY {
bootloader_process_apdu(rx, G_bootloader_mode);
// bootloader_process_apdu should throw EX_BOOTLOADER_RSK_END
ASSERT_FAIL();
}
CATCH(ERR_DEVICE_ONBOARDED) {
assert(!G_host_seed_is_set);
return;
}
CATCH_OTHER(e) {
ASSERT_FAIL();
}
FINALLY {
}
}
END_TRY;
}

void test_pin() {
printf("Test RSK_PIN_CMD...\n");

Expand Down Expand Up @@ -258,6 +294,40 @@ void test_wipe_default_mode() {
assert(RESET_IF_STARTED_CALLED());
}

void test_wipe_default_mode_onboarded() {
printf("Test RSK_WIPE (default mode) when onboarded...\n");

unsigned int rx;
unsigned int tx;
bootloader_init();
reset_flags();
G_bootloader_mode = BOOTLOADER_MODE_DEFAULT;
G_device_onboarded = 1;
G_is_onboarded = false;
G_is_pin_buffer_cleared = false;
G_is_pin_set = true;
SET_APDU("\x80\x07", rx); // RSK_WIPE
BEGIN_TRY {
TRY {
bootloader_process_apdu(rx, G_bootloader_mode);
// bootloader_process_apdu should throw EX_BOOTLOADER_RSK_END
ASSERT_FAIL();
}
CATCH(ERR_DEVICE_ONBOARDED) {
assert(!G_is_onboarded);
assert(!G_is_pin_buffer_cleared);
assert(G_is_pin_set);
return;
}
CATCH_OTHER(e) {
ASSERT_FAIL();
}
FINALLY {
}
}
END_TRY;
}

void test_wipe_onboard_mode() {
printf("Test RSK_WIPE (onboard mode)...\n");

Expand All @@ -277,6 +347,40 @@ void test_wipe_onboard_mode() {
assert(RESET_IF_STARTED_CALLED());
}

void test_wipe_onboard_mode_onboarded() {
printf("Test RSK_WIPE (onboard mode) when onboarded...\n");

unsigned int rx;
unsigned int tx;
bootloader_init();
reset_flags();
G_bootloader_mode = BOOTLOADER_MODE_ONBOARD;
G_device_onboarded = 1;
G_is_onboarded = false;
G_is_pin_buffer_cleared = false;
G_is_pin_set = true;
SET_APDU("\x80\x07", rx); // RSK_WIPE
BEGIN_TRY {
TRY {
bootloader_process_apdu(rx, G_bootloader_mode);
// bootloader_process_apdu should throw EX_BOOTLOADER_RSK_END
ASSERT_FAIL();
}
CATCH(ERR_DEVICE_ONBOARDED) {
assert(!G_is_onboarded);
assert(!G_is_pin_buffer_cleared);
assert(G_is_pin_set);
return;
}
CATCH_OTHER(e) {
ASSERT_FAIL();
}
FINALLY {
}
}
END_TRY;
}

void test_newpin() {
printf("Test RSK_NEWPIN...\n");

Expand Down Expand Up @@ -559,10 +663,13 @@ void test_onboard_mode() {
int main() {
test_init();
test_seed();
test_seed_onboarded();
test_pin();
test_is_onboard();
test_wipe_default_mode();
test_wipe_default_mode_onboarded();
test_wipe_onboard_mode();
test_wipe_onboard_mode_onboarded();
test_newpin();
test_echo();
test_mode();
Expand Down
7 changes: 3 additions & 4 deletions middleware/admin/onboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,9 @@ def do_onboard(options):
info(f"Onboarded: {bls(is_onboarded)}")

if is_onboarded:
message = ("WARNING: The following operation will wipe the device and "
"generate a new seed. This cannot be undone.")
else:
message = "The following operation will onboard the device."
raise AdminError("Device already onboarded")

message = "The following operation will onboard the device."
head([
message,
"Do you want to proceed? Yes/No",
Expand Down
3 changes: 3 additions & 0 deletions middleware/ledger/hsm2dongle.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ class HSM2DongleErrorResult(HSM2DongleBaseError):
def error_code(self):
return self.args[0]

def __str__(self):
return f"Dongle returned error code {hex(self.error_code)}"


# Handles low-level communication with a powHSM dongle
class HSM2Dongle:
Expand Down
41 changes: 30 additions & 11 deletions middleware/tests/admin/test_onboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def test_onboard(self, readline, get_hsm_unlock, get_admin_hsm,
get_admin_hsm.return_value = self.dongle

self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(side_effect=[False, True])
self.dongle.get_device_key = Mock(return_value=self.DEVICE_KEY)
self.dongle.setup_endorsement_key = Mock(return_value=self.ENDORSEMENT_KEY)
self.dongle.handshake = Mock()
Expand All @@ -112,7 +112,7 @@ def test_onboard(self, readline, get_hsm_unlock, get_admin_hsm,
with patch("builtins.open", mock_open()) as file_mock:
do_onboard(self.default_options)

self.assertEqual(info_mock.call_args_list[5][0][0], "Onboarded: Yes")
self.assertEqual(info_mock.call_args_list[5][0][0], "Onboarded: No")
self.assertEqual(info_mock.call_args_list[10][0][0], "Onboarded")
self.assertEqual(info_mock.call_args_list[14][0][0], "Device key gathered")
self.assertEqual(info_mock.call_args_list[16][0][0],
Expand All @@ -125,6 +125,25 @@ def test_onboard(self, readline, get_hsm_unlock, get_admin_hsm,
self.assertTrue(self.dongle.onboard.called)
self.assertTrue(self.dongle.handshake.called)

@patch("admin.onboard.get_admin_hsm")
@patch("admin.unlock.get_hsm")
@patch("sys.stdin.readline")
def test_onboard_already_onboarded(self, readline, get_hsm_unlock, get_admin_hsm,
get_hsm_onboard, info_mock, *_):
get_hsm_onboard.return_value = self.dongle
get_hsm_unlock.return_value = self.dongle
get_admin_hsm.return_value = self.dongle

self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)

with self.assertRaises(AdminError) as e:
do_onboard(self.default_options)

self.assertEqual(info_mock.call_args_list[5][0][0], "Onboarded: Yes")
self.assertEqual(e.exception.args[0], "Device already onboarded")
self.assertFalse(self.dongle.onboard.called)

@patch("admin.onboard.get_admin_hsm")
@patch("admin.unlock.get_hsm")
@patch("sys.stdin.readline")
Expand All @@ -135,7 +154,7 @@ def test_onboard_onboard_error(self, readline, get_hsm_unlock, get_admin_hsm,
get_admin_hsm.return_value = self.dongle

self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(return_value=False)
self.dongle.get_device_key = Mock()
self.dongle.setup_endorsement_key = Mock()
self.dongle.handshake = Mock()
Expand Down Expand Up @@ -163,7 +182,7 @@ def test_onboard_handshake_error(self, readline, get_hsm_unlock, get_admin_hsm,
get_admin_hsm.return_value = self.dongle

self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(side_effect=[False, True])
self.dongle.get_device_key = Mock()
self.dongle.setup_endorsement_key = Mock()
self.dongle.handshake = Mock(side_effect=HSM2DongleError("error-msg"))
Expand Down Expand Up @@ -191,7 +210,7 @@ def test_onboard_getkey_error(self, readline, get_hsm_unlock, get_admin_hsm,
get_admin_hsm.return_value = self.dongle

self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(side_effect=[False, True])
self.dongle.get_device_key = Mock(side_effect=HSM2DongleError("error-msg"))
self.dongle.setup_endorsement_key = Mock()
self.dongle.handshake = Mock()
Expand Down Expand Up @@ -219,7 +238,7 @@ def test_onboard_setupkey_error(self, readline, get_hsm_unlock, get_admin_hsm,
get_admin_hsm.return_value = self.dongle

self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(side_effect=[False, True])
self.dongle.get_device_key = Mock()
self.dongle.setup_endorsement_key = Mock(side_effect=HSM2DongleError("error-msg"))
self.dongle.handshake = Mock()
Expand All @@ -245,7 +264,7 @@ def test_onboard_user_cancelled(self, readline, hsm_unlock, hsm_admin,
hsm_onboard.return_value = self.dongle
hsm_unlock.return_value = self.dongle
self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(return_value=False)
self.dongle.onboard = Mock()
hsm_admin.return_value = self.dongle
readline.return_value = "no\n"
Expand All @@ -266,7 +285,7 @@ def test_onboard_no_output_file(self, readline, get_hsm, *_):
get_hsm.return_value = self.dongle

self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(return_value=False)
self.dongle.onboard = Mock()

options = self.default_options
Expand All @@ -288,7 +307,7 @@ def test_onboard_invalid_pin(self, *_):
def test_onboard_invalid_mode(self, get_hsm, *_):
get_hsm.return_value = self.dongle
self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.APP)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(return_value=False)

with self.assertRaises(AdminError) as e:
do_onboard(self.default_options)
Expand All @@ -305,7 +324,7 @@ def test_onboard_invalid_device_key(self, readline, get_hsm_unlock, get_admin_hs
get_admin_hsm.return_value = self.dongle

self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(side_effect=[False, True])
self.dongle.get_device_key = Mock(return_value=self.INVALID_KEY)
self.dongle.setup_endorsement_key = Mock(return_value=self.ENDORSEMENT_KEY)
readline.return_value = "yes\n"
Expand All @@ -326,7 +345,7 @@ def test_onboard_invalid_attestation_key(self, readline, get_hsm_unlock,
get_admin_hsm.return_value = self.dongle

self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
self.dongle.is_onboarded = Mock(return_value=True)
self.dongle.is_onboarded = Mock(side_effect=[False, True])
self.dongle.get_device_key = Mock(return_value=self.DEVICE_KEY)
self.dongle.setup_endorsement_key = Mock(return_value=self.INVALID_KEY)
readline.return_value = "yes\n"
Expand Down