feat(legacy): Implement UnlockPath.

pull/2850/head
Andrew Kozlik 1 year ago committed by matejcik
parent 6d20fccefd
commit a1afadfd01

@ -71,6 +71,8 @@ static uint8_t msg_resp[MSG_OUT_DECODED_SIZE] __attribute__((aligned));
// Authorization message type triggered by DoPreauthorized.
static MessageType authorization_type = 0;
static uint32_t unlock_path = 0;
#define RESP_INIT(TYPE) \
TYPE *resp = (TYPE *)(void *)msg_resp; \
_Static_assert(sizeof(msg_resp) >= sizeof(TYPE), #TYPE " is too large"); \
@ -407,6 +409,7 @@ void fsm_abortWorkflows(void) {
recovery_abort();
signing_abort();
authorization_type = 0;
unlock_path = 0;
#if !BITCOIN_ONLY
ethereum_signing_abort();
stellar_signingAbort();

@ -88,6 +88,7 @@ void fsm_msgGetOwnershipProof(const GetOwnershipProof *msg);
void fsm_msgAuthorizeCoinJoin(const AuthorizeCoinJoin *msg);
void fsm_msgCancelAuthorization(const CancelAuthorization *msg);
void fsm_msgDoPreauthorized(const DoPreauthorized *msg);
void fsm_msgUnlockPath(const UnlockPath *msg);
// crypto
void fsm_msgCipherKeyValue(const CipherKeyValue *msg);

@ -35,12 +35,14 @@ void fsm_msgGetPublicKey(const GetPublicKey *msg) {
curve = msg->ecdsa_curve_name;
}
// Do not allow access to SLIP25 paths.
if (msg->address_n_count > 0 && msg->address_n[0] == PATH_SLIP25_PURPOSE &&
config_getSafetyCheckLevel() == SafetyCheckLevel_Strict) {
fsm_sendFailure(FailureType_Failure_DataError, _("Forbidden key path"));
layoutHome();
return;
// UnlockPath is required to access SLIP25 paths.
if (msg->address_n_count > 0 && msg->address_n[0] == PATH_SLIP25_PURPOSE) {
// Verify that the desired path lies in the unlocked subtree.
if (msg->address_n[0] != unlock_path) {
fsm_sendFailure(FailureType_Failure_DataError, _("Forbidden key path"));
layoutHome();
return;
}
}
// derive m/0' to obtain root_fingerprint
@ -720,3 +722,65 @@ void fsm_msgDoPreauthorized(const DoPreauthorized *msg) {
msg_write(MessageType_MessageType_PreauthorizedRequest, resp);
layoutHome();
}
void fsm_msgUnlockPath(const UnlockPath *msg) {
(void)msg;
RESP_INIT(UnlockedPathRequest);
CHECK_INITIALIZED
CHECK_PIN
const char *KEYCHAIN_MAC_KEY_PATH[] = {"TREZOR", "Keychain MAC key"};
// UnlockPath is relevant only for SLIP-25 paths.
// Note: Currently we only allow unlocking the entire SLIP-25 purpose subtree
// instead of per-coin or per-account unlocking in order to avoid UI
// complexity.
if (msg->address_n_count != 1 || msg->address_n[0] != PATH_SLIP25_PURPOSE) {
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid path"));
layoutHome();
return;
}
uint8_t keychain_mac_key[32] = {0};
if (!fsm_getSlip21Key(KEYCHAIN_MAC_KEY_PATH, 2, keychain_mac_key)) {
return;
}
HMAC_SHA256_CTX hctx;
hmac_sha256_Init(&hctx, keychain_mac_key, sizeof(keychain_mac_key));
for (size_t i = 0; i < msg->address_n_count; ++i) {
hmac_sha256_Update(&hctx, (const uint8_t *)&msg->address_n[i],
sizeof(uint32_t));
}
hmac_sha256_Final(&hctx, resp->mac.bytes);
// Require confirmation to access SLIP25 paths unless already authorized.
if (msg->has_mac) {
uint8_t diff = 0;
for (size_t i = 0; i < SHA256_DIGEST_LENGTH; i++) {
diff |= (msg->mac.bytes[i] - resp->mac.bytes[i]);
}
if (msg->mac.size != SHA256_DIGEST_LENGTH || diff != 0) {
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid MAC"));
layoutHome();
return;
}
} else {
layoutConfirmCoinjoinAccess();
if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
layoutHome();
return;
}
}
unlock_path = msg->address_n[0];
resp->mac.size = SHA256_DIGEST_LENGTH;
resp->has_mac = true;
msg_write(MessageType_MessageType_UnlockedPathRequest, resp);
layoutHome();
}

@ -721,6 +721,12 @@ void layoutAuthorizeCoinJoin(const CoinInfo *coin, uint64_t max_rounds,
NULL, NULL);
}
void layoutConfirmCoinjoinAccess(void) {
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
_("Do you want to allow"), _("access to your"),
_("coinjoin account?"), NULL, NULL, NULL);
}
void layoutVerifyAddress(const CoinInfo *coin, const char *address) {
render_address_dialog(coin, address, _("Confirm address?"),
_("Message signed by:"), 0);

@ -76,6 +76,7 @@ void layoutConfirmNondefaultLockTime(uint32_t lock_time,
bool lock_time_disabled);
void layoutAuthorizeCoinJoin(const CoinInfo *coin, uint64_t max_rounds,
uint32_t max_fee_per_kvbyte);
void layoutConfirmCoinjoinAccess(void);
void layoutVerifyAddress(const CoinInfo *coin, const char *address);
void layoutCipherKeyValue(bool encrypt, const char *key);
void layoutEncryptMessage(const uint8_t *msg, uint32_t len, bool signing);

@ -4,7 +4,7 @@ endif
SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tezos WebAuthn \
DebugLinkRecordScreen DebugLinkEraseSdCard DebugLinkWatchLayout \
DebugLinkLayout GetNonce SetBusy UnlockPath \
DebugLinkLayout GetNonce SetBusy \
TxAckInput TxAckOutput TxAckPrev TxAckPaymentRequest \
EthereumSignTypedData EthereumTypedDataStructRequest EthereumTypedDataStructAck \
EthereumTypedDataValueRequest EthereumTypedDataValueAck

@ -43,9 +43,8 @@ PIN = "1234"
ROUND_ID_LEN = 32
SLIP25_PATH = parse_path("m/10025h")
pytestmark = pytest.mark.skip_t1
@pytest.mark.skip_t1
@pytest.mark.setup_client(pin=PIN)
def test_sign_tx(client: Client):
# NOTE: FAKE input tx
@ -252,6 +251,7 @@ def test_sign_tx(client: Client):
)
@pytest.mark.skip_t1
def test_sign_tx_large(client: Client):
# NOTE: FAKE input tx
@ -399,6 +399,7 @@ def test_sign_tx_large(client: Client):
assert delay <= max_expected_delay
@pytest.mark.skip_t1
def test_sign_tx_spend(client: Client):
# NOTE: FAKE input tx
@ -473,6 +474,7 @@ def test_sign_tx_spend(client: Client):
)
@pytest.mark.skip_t1
def test_wrong_coordinator(client: Client):
# Ensure that a preauthorized GetOwnershipProof fails if the commitment_data doesn't match the coordinator.
@ -499,6 +501,7 @@ def test_wrong_coordinator(client: Client):
)
@pytest.mark.skip_t1
def test_wrong_account_type(client: Client):
params = {
"client": client,
@ -525,6 +528,7 @@ def test_wrong_account_type(client: Client):
)
@pytest.mark.skip_t1
def test_cancel_authorization(client: Client):
# Ensure that a preauthorized GetOwnershipProof fails if the commitment_data doesn't match the coordinator.
@ -608,6 +612,7 @@ def test_get_public_key(client: Client):
assert resp.xpub == EXPECTED_XPUB
@pytest.mark.skip_t1
def test_get_address(client: Client):
# Ensure that the SLIP-0025 external chain is inaccessible without user confirmation.
with pytest.raises(TrezorFailure, match="Forbidden key path"):
@ -689,6 +694,7 @@ def test_get_address(client: Client):
)
@pytest.mark.skip_t1
def test_multisession_authorization(client: Client):
# Authorize CoinJoin with www.example1.com in session 1.
btc.authorize_coinjoin(

@ -1,6 +1,7 @@
{
"T1": {
"device_tests": {
"T1_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "9b3c916759b79048a4ab3e3fe8ce0ea0cf8d4ae6cfb66a5d712f21edfdb01782",
"T1_bitcoin-test_bcash.py::test_attack_change_input": "6111e313995d38c3970c92e48047fe4088c83666c64c6c859f69a232ad62829b",
"T1_bitcoin-test_bcash.py::test_send_bch_change": "6111e313995d38c3970c92e48047fe4088c83666c64c6c859f69a232ad62829b",
"T1_bitcoin-test_bcash.py::test_send_bch_multisig_change": "0962a2e630e06b6d20282cc241be40f41bc1648d0a26247c7c008f32a197d0cb",

Loading…
Cancel
Save