mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-06 23:39:38 +01:00
feat(connect): implement THP passphrase flow
This commit is contained in:
committed by
Szymon Lesisz
parent
de01a0302e
commit
dd0312cf0b
@@ -112,7 +112,9 @@ export class DeviceCurrentSession implements TypedCallProvider {
|
||||
expectedType: Messages.MessageKey | Messages.MessageKey[],
|
||||
msg: Messages.MessagePayload = {},
|
||||
) {
|
||||
if (!allowedCallsBeforeInitialize.includes(type) && !this.device?.features?.session_id) {
|
||||
const deviceSessionId =
|
||||
this.device.getThpState()?.sessionId || this.device?.features?.session_id;
|
||||
if (!allowedCallsBeforeInitialize.includes(type) && !deviceSessionId) {
|
||||
console.error(
|
||||
'Runtime',
|
||||
`typedCall: Device not initialized when calling ${type}. call Initialize first`,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { thpPairing } from './pairing';
|
||||
|
||||
export { abortThpWorkflow } from './thpCall';
|
||||
export { getThpCredentials } from './pairing';
|
||||
export { createThpSession } from './session';
|
||||
|
||||
export const getThpChannel = async (device: Device, withInteraction?: boolean) => {
|
||||
const thpState = device.getThpState();
|
||||
|
||||
29
packages/connect/src/device/thp/session.ts
Normal file
29
packages/connect/src/device/thp/session.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { thp as protocolThp } from '@trezor/protocol';
|
||||
|
||||
import type { Device } from '../Device';
|
||||
import { thpCall } from './thpCall';
|
||||
|
||||
export const createThpSession = async (device: Device, deriveCardano: boolean) => {
|
||||
let passphrase: protocolThp.ThpCreateNewSession;
|
||||
if (!device.features.passphrase_protection) {
|
||||
passphrase = { passphrase: '' };
|
||||
} else {
|
||||
// same flow as DeviceCurrentSession PassphraseRequest
|
||||
passphrase = await device.prompt('passphrase', {}).then(promptRes => {
|
||||
if (!promptRes.success) {
|
||||
return { passphrase: '' };
|
||||
}
|
||||
|
||||
return promptRes.payload.passphraseOnDevice
|
||||
? { on_device: true }
|
||||
: { passphrase: promptRes.payload.value };
|
||||
});
|
||||
}
|
||||
|
||||
await thpCall(device, 'ThpCreateNewSession', {
|
||||
...passphrase,
|
||||
derive_cardano: deriveCardano,
|
||||
});
|
||||
|
||||
return 0;
|
||||
};
|
||||
@@ -4,6 +4,20 @@ import { DEVICE, UI, createDeviceMessage, createUiMessage } from '../../events';
|
||||
import { StaticSessionId } from '../../types';
|
||||
import { WorkflowContext } from '../../types/workflow';
|
||||
import { toHardened } from '../../utils/pathUtils';
|
||||
import { createThpSession } from '../thp';
|
||||
|
||||
const getStaticSessionId = (device: WorkflowContext['device']) =>
|
||||
device
|
||||
.getCurrentSession()
|
||||
.typedCall('GetAddress', 'Address', {
|
||||
address_n: [toHardened(44), toHardened(1), toHardened(0), 0, 0],
|
||||
coin_name: 'Testnet',
|
||||
script_type: 'SPENDADDRESS',
|
||||
})
|
||||
.then(
|
||||
({ message }) =>
|
||||
`${message.address}@${device.features.device_id}:${device.getInstance()}` as StaticSessionId,
|
||||
);
|
||||
|
||||
const getState = async ({ device, method }: WorkflowContext) => {
|
||||
if (!device.features) return;
|
||||
@@ -21,13 +35,8 @@ const getState = async ({ device, method }: WorkflowContext) => {
|
||||
const expectedState = device.getState()?.staticSessionId;
|
||||
|
||||
// add-abort-signal
|
||||
const { message } = await device.getCurrentSession().typedCall('GetAddress', 'Address', {
|
||||
address_n: [toHardened(44), toHardened(1), toHardened(0), 0, 0],
|
||||
coin_name: 'Testnet',
|
||||
script_type: 'SPENDADDRESS',
|
||||
});
|
||||
|
||||
const uniqueState: StaticSessionId = `${message.address}@${device.features.device_id}:${device.getInstance()}`;
|
||||
const uniqueState = await getStaticSessionId(device);
|
||||
if (device.features.session_id) {
|
||||
device.setState({ sessionId: device.features.session_id });
|
||||
}
|
||||
@@ -73,17 +82,66 @@ const getInvalidDeviceState = async (
|
||||
});
|
||||
};
|
||||
|
||||
const getInvalidThpDeviceState = async (context: WorkflowContext) => {
|
||||
const { device, method } = context;
|
||||
const currentState = device.getState();
|
||||
const expectedState = currentState?.staticSessionId;
|
||||
const expectedSessionId = currentState?.sessionId
|
||||
? Buffer.from(currentState.sessionId, 'hex')
|
||||
: undefined;
|
||||
let uniqueState;
|
||||
const thpState = device.getThpState()!;
|
||||
if (expectedSessionId) {
|
||||
// validate that expected ThpSession still exists
|
||||
thpState.setSessionId(expectedSessionId);
|
||||
uniqueState = await getStaticSessionId(device).catch(e => {
|
||||
if (e.code === 'Failure_InvalidSession') {
|
||||
// requested sessionId is not valid, reset setSessionId
|
||||
device.setState({
|
||||
sessionId: undefined,
|
||||
deriveCardano: undefined,
|
||||
});
|
||||
thpState?.setSessionId(Buffer.alloc(1));
|
||||
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!uniqueState || (!currentState?.deriveCardano && method.useCardanoDerivation)) {
|
||||
const newSessionId = thpState.createNewSessionId();
|
||||
|
||||
await createThpSession(device, method.useCardanoDerivation);
|
||||
uniqueState = await getStaticSessionId(device);
|
||||
device.setState({
|
||||
sessionId: newSessionId.toString('hex'),
|
||||
deriveCardano: method.useCardanoDerivation,
|
||||
});
|
||||
}
|
||||
|
||||
if (expectedState && expectedState !== uniqueState) {
|
||||
return uniqueState;
|
||||
}
|
||||
|
||||
if (!expectedState) {
|
||||
device.setState({ staticSessionId: uniqueState });
|
||||
}
|
||||
};
|
||||
|
||||
export const validateState = async (context: WorkflowContext) => {
|
||||
const { device, method } = context;
|
||||
if (!method.useDeviceState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const validate =
|
||||
device.protocol.name === 'v2' ? getInvalidThpDeviceState : getInvalidDeviceState;
|
||||
|
||||
// Make sure that device will display pin/passphrase
|
||||
const isDeviceUnlocked = device.features.unlocked;
|
||||
const isUsingPopup = DataManager.getSettings('popup');
|
||||
try {
|
||||
let invalidDeviceState = await getInvalidDeviceState(context);
|
||||
let invalidDeviceState = await validate(context);
|
||||
if (isUsingPopup) {
|
||||
while (invalidDeviceState) {
|
||||
const uiPromise = method.createUiPromise(UI.INVALID_PASSPHRASE_ACTION, device);
|
||||
@@ -101,7 +159,7 @@ export const validateState = async (context: WorkflowContext) => {
|
||||
device.setState({ sessionId: undefined });
|
||||
await device.initialize(method.useCardanoDerivation);
|
||||
|
||||
invalidDeviceState = await getInvalidDeviceState(context);
|
||||
invalidDeviceState = await validate(context);
|
||||
} else {
|
||||
// set new state as requested
|
||||
device.setState({ staticSessionId: invalidDeviceState });
|
||||
|
||||
@@ -35,6 +35,8 @@ export class ThpState {
|
||||
private _expectedResponses: number[] = [];
|
||||
private _selectedMethod?: ThpPairingMethod;
|
||||
private _nfcSecret?: Buffer;
|
||||
private _sessionId: Buffer = Buffer.alloc(1);
|
||||
private _sessionIdCounter: number = 0;
|
||||
|
||||
get pairingTagPromise() {
|
||||
return this._pairingTagPromise;
|
||||
@@ -207,6 +209,28 @@ export class ThpState {
|
||||
};
|
||||
}
|
||||
|
||||
get sessionId() {
|
||||
return this._sessionId;
|
||||
}
|
||||
|
||||
createNewSessionId() {
|
||||
this._sessionIdCounter++;
|
||||
if (this._sessionIdCounter > 255) {
|
||||
this._sessionIdCounter = 1;
|
||||
}
|
||||
|
||||
const sessionId = Buffer.alloc(1);
|
||||
sessionId.writeUint8(this._sessionIdCounter, 0);
|
||||
|
||||
this.setSessionId(sessionId);
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
setSessionId(sessionId: Buffer) {
|
||||
this._sessionId = sessionId;
|
||||
}
|
||||
|
||||
serialize(): ThpStateSerialized {
|
||||
return {
|
||||
properties: this._properties,
|
||||
@@ -276,6 +300,8 @@ export class ThpState {
|
||||
this._pairingCredentials = [];
|
||||
this._selectedMethod = undefined;
|
||||
this._nfcSecret = undefined;
|
||||
this._sessionId = Buffer.alloc(1);
|
||||
this._sessionIdCounter = 0;
|
||||
}
|
||||
|
||||
toString() {
|
||||
|
||||
Reference in New Issue
Block a user