mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-02-20 00:33:07 +01:00
feat(suite): device invariability check UI
This commit is contained in:
committed by
Jiri Zbytovsky
parent
f5ed5a79ae
commit
0056141b1c
@@ -3,6 +3,7 @@ import '@suite-common/test-utils/src/globalOverrides';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
|
||||
import { AnalyticsState } from '@suite-common/analytics-redux';
|
||||
import { mockSuiteDevice } from '@suite-common/suite-types/mocks';
|
||||
import { TransportInfo } from '@trezor/connect';
|
||||
import * as envUtils from '@trezor/env-utils';
|
||||
import { DeepPartial } from '@trezor/type-utils';
|
||||
@@ -415,11 +416,7 @@ describe(`${Preloader.name} component`, () => {
|
||||
|
||||
it('Seedless device', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: {
|
||||
mode: 'seedless',
|
||||
features: {},
|
||||
authenticityChecks: {},
|
||||
},
|
||||
selectedDevice: mockSuiteDevice({ mode: 'seedless' }),
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
@@ -445,10 +442,7 @@ describe(`${Preloader.name} component`, () => {
|
||||
|
||||
it('Recovery mode device', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: {
|
||||
features: { recovery_status: 'Recovery' },
|
||||
authenticityChecks: {},
|
||||
},
|
||||
selectedDevice: mockSuiteDevice({}, { recovery_status: 'Recovery' }),
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
@@ -474,11 +468,7 @@ describe(`${Preloader.name} component`, () => {
|
||||
|
||||
it('Not initialized device', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: {
|
||||
mode: 'initialize',
|
||||
features: {},
|
||||
authenticityChecks: {},
|
||||
},
|
||||
selectedDevice: mockSuiteDevice({ mode: 'initialize' }),
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
@@ -504,11 +494,10 @@ describe(`${Preloader.name} component`, () => {
|
||||
|
||||
it('Bootloader device with installed firmware', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: {
|
||||
mode: 'bootloader',
|
||||
features: { firmware_present: true },
|
||||
authenticityChecks: {},
|
||||
},
|
||||
selectedDevice: mockSuiteDevice(
|
||||
{ mode: 'bootloader' },
|
||||
{ firmware_present: true, bootloader_mode: true },
|
||||
),
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
@@ -535,11 +524,10 @@ describe(`${Preloader.name} component`, () => {
|
||||
|
||||
it('Bootloader device without firmware', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: {
|
||||
mode: 'bootloader',
|
||||
features: { firmware_present: false },
|
||||
authenticityChecks: {},
|
||||
},
|
||||
selectedDevice: mockSuiteDevice(
|
||||
{ mode: 'bootloader' },
|
||||
{ firmware_present: false, bootloader_mode: true },
|
||||
),
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
@@ -585,11 +573,7 @@ describe(`${Preloader.name} component`, () => {
|
||||
|
||||
it('Required FW update device', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: {
|
||||
firmware: 'required',
|
||||
features: {},
|
||||
authenticityChecks: {},
|
||||
},
|
||||
selectedDevice: mockSuiteDevice({ firmware: 'required' }),
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { messageSystemInitialState } from '@suite-common/message-system';
|
||||
import { mockSuiteDevice } from '@suite-common/suite-types/mocks';
|
||||
import * as deviceUtils from '@suite-common/suite-utils';
|
||||
import { defaultDevicePersistentData } from '@suite-common/wallet-core/src/support/deviceMocks';
|
||||
import { DeviceModelInternal } from '@trezor/device-utils';
|
||||
|
||||
import { initialAppState } from 'src/support/tests/__fixtures__/defaultAppState';
|
||||
import { AcquiredDevice, AppState } from 'src/types/suite';
|
||||
@@ -26,15 +26,21 @@ const authenticityChecksFail: AcquiredDevice['authenticityChecks'] = {
|
||||
|
||||
const defaultDevice = mockSuiteDevice();
|
||||
if (!deviceUtils.isDeviceAcquired(defaultDevice)) {
|
||||
throw 'mockSuiteDevice() must return an AcquiredDevice here.';
|
||||
throw `${mockSuiteDevice.name}() must return an AcquiredDevice here.`;
|
||||
}
|
||||
// derived from this device
|
||||
const matchingDevicePersistentData = {
|
||||
...defaultDevicePersistentData,
|
||||
device_id: defaultDevice.features.device_id,
|
||||
unit_color: defaultDevice.features.unit_color,
|
||||
internal_model: defaultDevice.features.internal_model,
|
||||
};
|
||||
|
||||
const fixtures: Fixture[] = [
|
||||
{
|
||||
description: 'returns false if all checks pass',
|
||||
state: {
|
||||
...initialAppState,
|
||||
messageSystem: messageSystemInitialState,
|
||||
device: {
|
||||
...initialAppState.device,
|
||||
selectedDevice: {
|
||||
@@ -58,7 +64,6 @@ const fixtures: Fixture[] = [
|
||||
app: 'settings',
|
||||
},
|
||||
},
|
||||
messageSystem: messageSystemInitialState,
|
||||
device: {
|
||||
...initialAppState.device,
|
||||
selectedDevice: {
|
||||
@@ -73,7 +78,6 @@ const fixtures: Fixture[] = [
|
||||
description: 'returns true if firmware check errored and not dismissed',
|
||||
state: {
|
||||
...initialAppState,
|
||||
messageSystem: messageSystemInitialState,
|
||||
device: {
|
||||
...initialAppState.device,
|
||||
selectedDevice: {
|
||||
@@ -88,7 +92,6 @@ const fixtures: Fixture[] = [
|
||||
description: 'returns false if firmware check errored and dismissed',
|
||||
state: {
|
||||
...initialAppState,
|
||||
messageSystem: messageSystemInitialState,
|
||||
device: {
|
||||
...initialAppState.device,
|
||||
dismissedSecurityChecks: { firmwareAuthenticity: ['device-id'] },
|
||||
@@ -104,7 +107,6 @@ const fixtures: Fixture[] = [
|
||||
description: 'returns false if a firmware check errored but is disabled',
|
||||
state: {
|
||||
...initialAppState,
|
||||
messageSystem: messageSystemInitialState,
|
||||
device: {
|
||||
...initialAppState.device,
|
||||
selectedDevice: {
|
||||
@@ -132,12 +134,11 @@ const fixtures: Fixture[] = [
|
||||
description: 'returns true if entropy check errored',
|
||||
state: {
|
||||
...initialAppState,
|
||||
messageSystem: messageSystemInitialState,
|
||||
device: {
|
||||
...initialAppState.device,
|
||||
persistentDeviceData: [
|
||||
{
|
||||
...defaultDevicePersistentData,
|
||||
...matchingDevicePersistentData,
|
||||
lastEntropyCheckResult: { success: false },
|
||||
},
|
||||
],
|
||||
@@ -153,12 +154,11 @@ const fixtures: Fixture[] = [
|
||||
description: 'returns false if entropy check errored but is disabled',
|
||||
state: {
|
||||
...initialAppState,
|
||||
messageSystem: messageSystemInitialState,
|
||||
device: {
|
||||
...initialAppState.device,
|
||||
persistentDeviceData: [
|
||||
{
|
||||
...defaultDevicePersistentData,
|
||||
...matchingDevicePersistentData,
|
||||
lastEntropyCheckResult: { success: false },
|
||||
},
|
||||
],
|
||||
@@ -180,6 +180,33 @@ const fixtures: Fixture[] = [
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
description: 'returns true for a device with an invalid id',
|
||||
state: {
|
||||
...initialAppState,
|
||||
device: { ...initialAppState.device, selectedDevice: { ...defaultDevice, id: null } },
|
||||
},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
description: 'returns true for a device with mismatch against its persistent data',
|
||||
state: {
|
||||
...initialAppState,
|
||||
device: {
|
||||
...initialAppState.device,
|
||||
persistentDeviceData: [matchingDevicePersistentData],
|
||||
selectedDevice: {
|
||||
...defaultDevice,
|
||||
features: {
|
||||
...defaultDevice.features,
|
||||
internal_model: DeviceModelInternal.T1B1,
|
||||
unit_color: 333,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: true,
|
||||
},
|
||||
];
|
||||
|
||||
describe(selectShouldDisplayDeviceCompromisedOnRoute.name, () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { TranslationKey } from '@suite/intl';
|
||||
import { SkippedHashCheckError } from '@suite-common/firmware-authenticity';
|
||||
import {
|
||||
getIsDeviceIdValid,
|
||||
selectIsDeviceInvariabilityCheckSuccess,
|
||||
selectSelectedDevice,
|
||||
selectWasFwHashCheckOtherErrorLastTime,
|
||||
} from '@suite-common/wallet-core';
|
||||
@@ -36,6 +37,7 @@ const hashCheckSubtitleMap: Record<
|
||||
|
||||
const DeviceCompromisedContent = () => {
|
||||
const isValidId = getIsDeviceIdValid(useSelector(selectSelectedDevice));
|
||||
const isDeviceInvariabilityCheckSuccess = useSelector(selectIsDeviceInvariabilityCheckSuccess);
|
||||
const revisionCheckError = useSelector(selectFirmwareRevisionCheckErrorIfEnabled);
|
||||
const hashCheckError = useSelector(selectFirmwareHashCheckErrorIfEnabled);
|
||||
const isEntropyCheckFailed = useSelector(selectIsEntropyCheckEnabledAndFailed);
|
||||
@@ -52,6 +54,17 @@ const DeviceCompromisedContent = () => {
|
||||
/>
|
||||
);
|
||||
}
|
||||
// this check is only a precaution, not expected to be seen often
|
||||
if (!isDeviceInvariabilityCheckSuccess) {
|
||||
return (
|
||||
<SecurityCheckFail
|
||||
ctaSection={<FwAuthenticityCheckSupportButton />}
|
||||
heading="TR_DEVICE_COMPROMISED_HEADING"
|
||||
text="TR_DEVICE_COMPROMISED_INVARIABILITY_CHECK_FAILED_TEXT"
|
||||
checklistItems={hardFailureChecklistItems}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isEntropyCheckFailed) {
|
||||
return (
|
||||
<SecurityCheckFail
|
||||
|
||||
@@ -5,6 +5,7 @@ import { mockSuiteDevice } from '@suite-common/suite-types/mocks';
|
||||
import * as deviceUtils from '@suite-common/suite-utils';
|
||||
import { DeviceReducerState, deviceInitialState } from '@suite-common/wallet-core';
|
||||
import { defaultDevicePersistentData } from '@suite-common/wallet-core/src/support/deviceMocks';
|
||||
import { DeviceModelInternal } from '@trezor/device-utils';
|
||||
|
||||
import { AppState } from 'src/reducers/store';
|
||||
import { initialAppState } from 'src/support/tests/__fixtures__/defaultAppState';
|
||||
@@ -50,6 +51,13 @@ const defaultDevice = mockSuiteDevice();
|
||||
if (!deviceUtils.isDeviceAcquired(defaultDevice)) {
|
||||
throw 'mockSuiteDevice() must return an AcquiredDevice here.';
|
||||
}
|
||||
// derived from this device
|
||||
const matchingDevicePersistentData = {
|
||||
...defaultDevicePersistentData,
|
||||
device_id: defaultDevice.features.device_id,
|
||||
unit_color: defaultDevice.features.unit_color,
|
||||
internal_model: defaultDevice.features.internal_model,
|
||||
};
|
||||
|
||||
const deviceCompromisedFixtures: Array<{
|
||||
description: string;
|
||||
@@ -57,12 +65,12 @@ const deviceCompromisedFixtures: Array<{
|
||||
result: TranslationKey;
|
||||
}> = [
|
||||
{
|
||||
description: 'Errored entropy check',
|
||||
description: 'Entropy check error',
|
||||
device: {
|
||||
...deviceInitialState,
|
||||
persistentDeviceData: [
|
||||
{
|
||||
...defaultDevicePersistentData,
|
||||
...matchingDevicePersistentData,
|
||||
lastEntropyCheckResult: { success: false },
|
||||
},
|
||||
],
|
||||
@@ -71,7 +79,7 @@ const deviceCompromisedFixtures: Array<{
|
||||
result: 'TR_DEVICE_COMPROMISED_ENTROPY_CHECK_TEXT',
|
||||
},
|
||||
{
|
||||
description: 'Errored firmware hash check',
|
||||
description: 'Firmware hash check error',
|
||||
device: {
|
||||
...deviceInitialState,
|
||||
selectedDevice: {
|
||||
@@ -131,7 +139,7 @@ const deviceCompromisedFixtures: Array<{
|
||||
result: 'TR_FAILED_VERIFY_DEVICE_AGAIN_TEXT',
|
||||
},
|
||||
{
|
||||
description: 'Errored firmware revision check',
|
||||
description: 'Firmware revision check error',
|
||||
device: {
|
||||
...deviceInitialState,
|
||||
selectedDevice: {
|
||||
@@ -147,6 +155,27 @@ const deviceCompromisedFixtures: Array<{
|
||||
},
|
||||
result: 'TR_DEVICE_COMPROMISED_FW_REVISION_CHECK_TEXT',
|
||||
},
|
||||
{
|
||||
description: 'Device Id check error',
|
||||
device: { ...initialAppState.device, selectedDevice: { ...defaultDevice, id: null } },
|
||||
result: 'TR_DEVICE_COMPROMISED_INVALID_ID_TEXT',
|
||||
},
|
||||
{
|
||||
description: 'Device invariability check error',
|
||||
device: {
|
||||
...initialAppState.device,
|
||||
persistentDeviceData: [matchingDevicePersistentData],
|
||||
selectedDevice: {
|
||||
...defaultDevice,
|
||||
features: {
|
||||
...defaultDevice.features,
|
||||
internal_model: DeviceModelInternal.T1B1,
|
||||
unit_color: 333,
|
||||
},
|
||||
},
|
||||
},
|
||||
result: 'TR_DEVICE_COMPROMISED_INVARIABILITY_CHECK_FAILED_TEXT',
|
||||
},
|
||||
];
|
||||
|
||||
describe(`${DeviceCompromised.name} component`, () => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
getIsDeviceIdValid,
|
||||
selectFirmwareHashCheckError,
|
||||
selectFirmwareRevisionCheckError,
|
||||
selectIsDeviceInvariabilityCheckSuccess,
|
||||
selectIsEntropyCheckFailed,
|
||||
selectIsFirmwareAuthenticityCheckDismissed,
|
||||
selectSelectedDevice,
|
||||
@@ -97,6 +98,8 @@ export const selectIsEntropyCheckEnabledAndFailed = (state: AppState) => {
|
||||
|
||||
export const selectShouldDisplayDeviceCompromised = (state: AppState): boolean => {
|
||||
const isDeviceIdValid = getIsDeviceIdValid(selectSelectedDevice(state));
|
||||
const deviceInvariabilitySuccess = selectIsDeviceInvariabilityCheckSuccess(state);
|
||||
|
||||
const isFirmwareCheckEnabledAndFailed =
|
||||
selectIsFirmwareAuthenticityCheckEnabledAndHardFailed(state);
|
||||
const isFirmwareAuthenticityCheckDismissed = selectIsFirmwareAuthenticityCheckDismissed(state);
|
||||
@@ -106,6 +109,7 @@ export const selectShouldDisplayDeviceCompromised = (state: AppState): boolean =
|
||||
|
||||
return (
|
||||
!isDeviceIdValid ||
|
||||
!deviceInvariabilitySuccess ||
|
||||
(!isFirmwareAuthenticityCheckDismissed && isFirmwareCheckEnabledAndFailed) ||
|
||||
isEntropyCheckEnabledAndFailed
|
||||
);
|
||||
|
||||
@@ -7255,6 +7255,10 @@ export const messages = defineMessages({
|
||||
id: 'TR_DEVICE_COMPROMISED_INVALID_ID_TEXT',
|
||||
defaultMessage: 'Security check (id validity check) has failed.',
|
||||
},
|
||||
TR_DEVICE_COMPROMISED_INVARIABILITY_CHECK_FAILED_TEXT: {
|
||||
id: 'TR_DEVICE_COMPROMISED_INVARIABILITY_CHECK_FAILED_TEXT',
|
||||
defaultMessage: 'Your device manipulates its model or color.',
|
||||
},
|
||||
TR_DEVICE_COMPROMISED_FW_HASH_CHECK_TEXT: {
|
||||
id: 'TR_DEVICE_COMPROMISED_FW_HASH_CHECK_TEXT',
|
||||
defaultMessage: 'Your device firmware hash check has failed.',
|
||||
|
||||
Reference in New Issue
Block a user