diff --git a/legacy/firmware/config.c b/legacy/firmware/config.c index 485ac8616b..a5173137b3 100644 --- a/legacy/firmware/config.c +++ b/legacy/firmware/config.c @@ -122,6 +122,9 @@ be added to the storage u2f_counter to get the real counter value. static secbool sessionSeedCached, sessionSeedUsesPassphrase; static uint8_t CONFIDENTIAL sessionSeed[64]; +static secbool sessionIdCached; +static uint8_t sessionId[32]; + static secbool sessionPassphraseCached = secfalse; static char CONFIDENTIAL sessionPassphrase[51]; @@ -409,6 +412,8 @@ void session_clear(bool lock) { memzero(&sessionSeed, sizeof(sessionSeed)); sessionPassphraseCached = secfalse; memzero(&sessionPassphrase, sizeof(sessionPassphrase)); + sessionIdCached = secfalse; + memzero(&sessionId, sizeof(sessionId)); if (lock) { storage_lock(); } @@ -527,8 +532,6 @@ void config_setLanguage(const char *lang) { } void config_setPassphraseProtection(bool passphrase_protection) { - sessionSeedCached = secfalse; - sessionPassphraseCached = secfalse; config_set_bool(KEY_PASSPHRASE_PROTECTION, passphrase_protection); } @@ -552,6 +555,7 @@ static void get_root_node_callback(uint32_t iter, uint32_t total) { const uint8_t *config_getSeed(bool usePassphrase) { // root node is properly cached + // TODO: investigate if (usePassphrase == (sectrue == sessionSeedUsesPassphrase) && sectrue == sessionSeedCached) { return sessionSeed; @@ -820,31 +824,12 @@ bool session_isPassphraseCached(void) { return sectrue == sessionPassphraseCached; } -bool session_getState(const uint8_t *salt, uint8_t *state, - const char *passphrase) { - if (!passphrase && sectrue != sessionPassphraseCached) { - return false; - } else { - passphrase = sessionPassphrase; +const uint8_t *session_getSessionId(void) { + if (!sessionIdCached) { + random_buffer(sessionId, 32); } - if (!salt) { - // if salt is not provided fill the first half of the state with random data - random_buffer(state, 32); - } else { - // if salt is provided fill the first half of the state with salt - memcpy(state, salt, 32); - } - // state[0:32] = salt - // state[32:64] = HMAC(passphrase, salt || device_id) - HMAC_SHA256_CTX ctx = {0}; - hmac_sha256_Init(&ctx, (const uint8_t *)passphrase, strlen(passphrase)); - hmac_sha256_Update(&ctx, state, 32); - hmac_sha256_Update(&ctx, (const uint8_t *)config_uuid, sizeof(config_uuid)); - hmac_sha256_Final(&ctx, state + 32); - - memzero(&ctx, sizeof(ctx)); - - return true; + sessionIdCached = sectrue; + return sessionId; } bool session_isUnlocked(void) { return sectrue == storage_is_unlocked(); } diff --git a/legacy/firmware/config.h b/legacy/firmware/config.h index d061dea71f..b16fce71dc 100644 --- a/legacy/firmware/config.h +++ b/legacy/firmware/config.h @@ -110,8 +110,7 @@ void config_setHomescreen(const uint8_t *data, uint32_t size); void session_cachePassphrase(const char *passphrase); bool session_isPassphraseCached(void); -bool session_getState(const uint8_t *salt, uint8_t *state, - const char *passphrase); +const uint8_t *session_getSessionId(void); bool config_setMnemonic(const char *mnemonic); bool config_containsMnemonic(const char *mnemonic); diff --git a/legacy/firmware/fsm_msg_common.h b/legacy/firmware/fsm_msg_common.h index 9de5b2ec40..25667ea57b 100644 --- a/legacy/firmware/fsm_msg_common.h +++ b/legacy/firmware/fsm_msg_common.h @@ -20,17 +20,13 @@ void fsm_msgInitialize(const Initialize *msg) { recovery_abort(); signing_abort(); - if (msg && msg->has_state && msg->state.size == 64) { - uint8_t i_state[64]; - if (!session_getState(msg->state.bytes, i_state, NULL)) { - session_clear(false); // do not clear PIN - } else { - if (0 != memcmp(msg->state.bytes, i_state, 64)) { - session_clear(false); // do not clear PIN - } + if (msg && msg->has_session_id && msg->session_id.size == 32) { + if (0 != memcmp(session_getSessionId(), msg->session_id.bytes, 32)) { + session_clear(false); // do not lock } } else { - session_clear(false); // do not clear PIN + // If session id was not specified -> clear the cache. + session_clear(false); // do not lock } layoutHome(); fsm_msgGetFeatures(0); @@ -39,6 +35,12 @@ void fsm_msgInitialize(const Initialize *msg) { void fsm_msgGetFeatures(const GetFeatures *msg) { (void)msg; RESP_INIT(Features); + + resp->has_session_id = true; + memcpy(resp->session_id.bytes, session_getSessionId(), + sizeof(resp->session_id.bytes)); + resp->session_id.size = sizeof(resp->session_id.bytes); + resp->has_vendor = true; strlcpy(resp->vendor, "trezor.io", sizeof(resp->vendor)); resp->has_major_version = true; @@ -359,6 +361,10 @@ void fsm_msgClearSession(const ClearSession *msg) { } void fsm_msgApplySettings(const ApplySettings *msg) { + CHECK_PARAM( + !msg->has_passphrase_always_on_device, + _("This firmware is incapable of passphrase entry on the device.")); + CHECK_PARAM(msg->has_label || msg->has_language || msg->has_use_passphrase || msg->has_homescreen || msg->has_auto_lock_delay_ms, _("No setting provided")); diff --git a/legacy/firmware/protect.c b/legacy/firmware/protect.c index 2b77b291db..4196d58844 100644 --- a/legacy/firmware/protect.c +++ b/legacy/firmware/protect.c @@ -354,6 +354,7 @@ bool protectPassphrase(void) { bool passphrase_protection = false; config_getPassphraseProtection(&passphrase_protection); if (!passphrase_protection || session_isPassphraseCached()) { + session_cachePassphrase(""); return true; } @@ -369,11 +370,15 @@ bool protectPassphrase(void) { bool result; for (;;) { usbPoll(); - // TODO: correctly process PassphraseAck with state field set (mismatch => - // Failure) if (msg_tiny_id == MessageType_MessageType_PassphraseAck) { msg_tiny_id = 0xFFFF; PassphraseAck *ppa = (PassphraseAck *)msg_tiny; + if (ppa->has_on_device && ppa->on_device == true) { + fsm_sendFailure( + FailureType_Failure_DataError, + _("This firmware is incapable of passphrase entry on the device.")); + // TODO: write test + } session_cachePassphrase(ppa->has_passphrase ? ppa->passphrase : ""); result = true; break; diff --git a/legacy/firmware/protob/messages-common.options b/legacy/firmware/protob/messages-common.options index cbb39f1cf6..6060e696bd 100644 --- a/legacy/firmware/protob/messages-common.options +++ b/legacy/firmware/protob/messages-common.options @@ -5,9 +5,6 @@ Failure.message max_size:256 PinMatrixAck.pin max_size:10 PassphraseAck.passphrase max_size:51 -PassphraseAck.state max_size:64 - -PassphraseStateRequest.state max_size:64 HDNodeType.chain_code max_size:32 HDNodeType.private_key max_size:32 diff --git a/legacy/firmware/protob/messages-management.options b/legacy/firmware/protob/messages-management.options index bd7971d7c4..c775377365 100644 --- a/legacy/firmware/protob/messages-management.options +++ b/legacy/firmware/protob/messages-management.options @@ -1,4 +1,4 @@ -Initialize.state max_size:64 +Initialize.session_id max_size:32 Features.vendor max_size:33 Features.device_id max_size:25 @@ -10,6 +10,7 @@ Features.model max_size:17 Features.fw_vendor max_size:256 Features.fw_vendor_keys max_size:32 Features.capabilities max_count:32 +Features.session_id max_size:32 ApplySettings.language max_size:17 ApplySettings.label max_size:33 diff --git a/tests/device_tests/test_session_id_and_passphrase.py b/tests/device_tests/test_session_id_and_passphrase.py index a90f2e0c6a..c5ab3ace6f 100644 --- a/tests/device_tests/test_session_id_and_passphrase.py +++ b/tests/device_tests/test_session_id_and_passphrase.py @@ -39,7 +39,6 @@ def _enable_passphrase(client): assert isinstance(response, messages.Success) -@pytest.mark.skip_t1 # TODO @pytest.mark.skip_ui @pytest.mark.setup_client() def test_session_with_passphrase(client): @@ -91,7 +90,6 @@ def test_session_with_passphrase(client): ) -@pytest.mark.skip_t1 # TODO @pytest.mark.skip_ui @pytest.mark.setup_client() def test_session_enable_passphrase(client): @@ -132,7 +130,6 @@ def test_session_enable_passphrase(client): ) -@pytest.mark.skip_t1 @pytest.mark.skip_ui @pytest.mark.setup_client() def test_passphrase_always_on_device(client): @@ -146,6 +143,12 @@ def test_passphrase_always_on_device(client): # Force passphrase entry on Trezor. response = client.call_raw(messages.ApplySettings(passphrase_always_on_device=True)) + + if client.features.model == "1": + assert isinstance(response, messages.Failure) + assert response.code == 3 # DataError + return + assert isinstance(response, messages.ButtonRequest) # confirm dialog client.debug.press_yes() response = client.call_raw(messages.ButtonAck())