@ledgerhq/device-core 0.2.1-next.1 → 0.3.0-nightly.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +9 -9
- package/lib/commands/entities/AppStorageInfo.d.ts +8 -0
- package/lib/commands/entities/AppStorageInfo.d.ts.map +1 -0
- package/lib/commands/entities/AppStorageInfo.js +3 -0
- package/lib/commands/entities/AppStorageInfo.js.map +1 -0
- package/lib/commands/use-cases/app-backup/backupAppStorage.d.ts +18 -0
- package/lib/commands/use-cases/app-backup/backupAppStorage.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/backupAppStorage.js +98 -0
- package/lib/commands/use-cases/app-backup/backupAppStorage.js.map +1 -0
- package/lib/commands/use-cases/app-backup/backupAppStorage.test.d.ts +2 -0
- package/lib/commands/use-cases/app-backup/backupAppStorage.test.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/backupAppStorage.test.js +56 -0
- package/lib/commands/use-cases/app-backup/backupAppStorage.test.js.map +1 -0
- package/lib/commands/use-cases/app-backup/getAppStorageInfo.d.ts +20 -0
- package/lib/commands/use-cases/app-backup/getAppStorageInfo.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/getAppStorageInfo.js +104 -0
- package/lib/commands/use-cases/app-backup/getAppStorageInfo.js.map +1 -0
- package/lib/commands/use-cases/app-backup/getAppStorageInfo.test.d.ts +2 -0
- package/lib/commands/use-cases/app-backup/getAppStorageInfo.test.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/getAppStorageInfo.test.js +59 -0
- package/lib/commands/use-cases/app-backup/getAppStorageInfo.test.js.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorage.d.ts +12 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorage.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorage.js +93 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorage.js.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorage.test.d.ts +2 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorage.test.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorage.test.js +54 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorage.test.js.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageCommit.d.ts +11 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageCommit.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageCommit.js +83 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageCommit.js.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageCommit.test.d.ts +2 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageCommit.test.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageCommit.test.js +49 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageCommit.test.js.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageInit.d.ts +13 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageInit.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageInit.js +99 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageInit.js.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageInit.test.d.ts +2 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageInit.test.d.ts.map +1 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageInit.test.js +52 -0
- package/lib/commands/use-cases/app-backup/restoreAppStorageInit.test.js.map +1 -0
- package/lib/commands/use-cases/getDeviceName.d.ts.map +1 -1
- package/lib/commands/use-cases/getDeviceName.js +15 -0
- package/lib/commands/use-cases/getDeviceName.js.map +1 -1
- package/lib/errors.d.ts +40 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +30 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +11 -1
- package/lib/index.js.map +1 -1
- package/lib-es/commands/entities/AppStorageInfo.d.ts +8 -0
- package/lib-es/commands/entities/AppStorageInfo.d.ts.map +1 -0
- package/lib-es/commands/entities/AppStorageInfo.js +2 -0
- package/lib-es/commands/entities/AppStorageInfo.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/backupAppStorage.d.ts +18 -0
- package/lib-es/commands/use-cases/app-backup/backupAppStorage.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/backupAppStorage.js +93 -0
- package/lib-es/commands/use-cases/app-backup/backupAppStorage.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/backupAppStorage.test.d.ts +2 -0
- package/lib-es/commands/use-cases/app-backup/backupAppStorage.test.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/backupAppStorage.test.js +54 -0
- package/lib-es/commands/use-cases/app-backup/backupAppStorage.test.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/getAppStorageInfo.d.ts +20 -0
- package/lib-es/commands/use-cases/app-backup/getAppStorageInfo.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/getAppStorageInfo.js +99 -0
- package/lib-es/commands/use-cases/app-backup/getAppStorageInfo.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/getAppStorageInfo.test.d.ts +2 -0
- package/lib-es/commands/use-cases/app-backup/getAppStorageInfo.test.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/getAppStorageInfo.test.js +57 -0
- package/lib-es/commands/use-cases/app-backup/getAppStorageInfo.test.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorage.d.ts +12 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorage.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorage.js +88 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorage.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorage.test.d.ts +2 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorage.test.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorage.test.js +52 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorage.test.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageCommit.d.ts +11 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageCommit.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageCommit.js +78 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageCommit.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageCommit.test.d.ts +2 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageCommit.test.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageCommit.test.js +47 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageCommit.test.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageInit.d.ts +13 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageInit.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageInit.js +94 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageInit.js.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageInit.test.d.ts +2 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageInit.test.d.ts.map +1 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageInit.test.js +50 -0
- package/lib-es/commands/use-cases/app-backup/restoreAppStorageInit.test.js.map +1 -0
- package/lib-es/commands/use-cases/getDeviceName.d.ts.map +1 -1
- package/lib-es/commands/use-cases/getDeviceName.js +15 -0
- package/lib-es/commands/use-cases/getDeviceName.js.map +1 -1
- package/lib-es/errors.d.ts +40 -0
- package/lib-es/errors.d.ts.map +1 -0
- package/lib-es/errors.js +27 -0
- package/lib-es/errors.js.map +1 -0
- package/lib-es/index.d.ts +6 -0
- package/lib-es/index.d.ts.map +1 -1
- package/lib-es/index.js +5 -0
- package/lib-es/index.js.map +1 -1
- package/package.json +6 -6
- package/src/commands/entities/AppStorageInfo.ts +7 -0
- package/src/commands/use-cases/app-backup/backupAppStorage.test.ts +52 -0
- package/src/commands/use-cases/app-backup/backupAppStorage.ts +97 -0
- package/src/commands/use-cases/app-backup/getAppStorageInfo.test.ts +63 -0
- package/src/commands/use-cases/app-backup/getAppStorageInfo.ts +102 -0
- package/src/commands/use-cases/app-backup/restoreAppStorage.test.ts +57 -0
- package/src/commands/use-cases/app-backup/restoreAppStorage.ts +93 -0
- package/src/commands/use-cases/app-backup/restoreAppStorageCommit.test.ts +45 -0
- package/src/commands/use-cases/app-backup/restoreAppStorageCommit.ts +82 -0
- package/src/commands/use-cases/app-backup/restoreAppStorageInit.test.ts +55 -0
- package/src/commands/use-cases/app-backup/restoreAppStorageInit.ts +96 -0
- package/src/commands/use-cases/getDeviceName.ts +16 -0
- package/src/errors.ts +29 -0
- package/src/index.ts +6 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import Transport, { StatusCodes, TransportStatusError } from "@ledgerhq/hw-transport";
|
|
2
|
+
import { LocalTracer } from "@ledgerhq/logs";
|
|
3
|
+
import type { APDU } from "../../entities/APDU";
|
|
4
|
+
import {
|
|
5
|
+
GenerateAesKeyFailed,
|
|
6
|
+
InternalComputeAesCmacFailed,
|
|
7
|
+
InternalCryptoOperationFailed,
|
|
8
|
+
InvalidBackupState,
|
|
9
|
+
InvalidContext,
|
|
10
|
+
} from "../../../errors";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Name in documentation: INS_APP_STORAGE_BACKUP
|
|
14
|
+
* cla: 0xe0
|
|
15
|
+
* ins: 0x6b
|
|
16
|
+
* p1: 0x00
|
|
17
|
+
* p2: 0x00
|
|
18
|
+
* lc: 0x00
|
|
19
|
+
*/
|
|
20
|
+
const BACKUP_APP_STORAGE = [0xe0, 0x6b, 0x00, 0x00] as const;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 0x9000: Success.
|
|
24
|
+
* 0x5123: Invalid context, Get Info must be called.
|
|
25
|
+
* 0x5419: Failed to generate AES key.
|
|
26
|
+
* 0x541A: Internal error, crypto operation failed.
|
|
27
|
+
* 0x541B: Internal error, failed to compute AES CMAC.
|
|
28
|
+
* 0x541C: Failed to encrypt the app storage backup.
|
|
29
|
+
* 0x662F: Invalid device state, recovery mode.
|
|
30
|
+
* 0x6642: Invalid backup state, backup already performed.
|
|
31
|
+
*/
|
|
32
|
+
const RESPONSE_STATUS_SET: number[] = [
|
|
33
|
+
StatusCodes.OK,
|
|
34
|
+
StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT,
|
|
35
|
+
StatusCodes.GEN_AES_KEY_FAILED,
|
|
36
|
+
StatusCodes.INTERNAL_CRYPTO_OPERATION_FAILED,
|
|
37
|
+
StatusCodes.INTERNAL_COMPUTE_AES_CMAC_FAILED,
|
|
38
|
+
StatusCodes.ENCRYPT_APP_STORAGE_FAILED,
|
|
39
|
+
StatusCodes.DEVICE_IN_RECOVERY_MODE,
|
|
40
|
+
StatusCodes.INVALID_BACKUP_STATE,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Retrieves the app storage information (chunk) from the device and returns it
|
|
45
|
+
* as a buffer.
|
|
46
|
+
*
|
|
47
|
+
* @param transport - The transport object used to communicate with the device.
|
|
48
|
+
* @returns A promise that resolves to the app storage information as a buffer.
|
|
49
|
+
*/
|
|
50
|
+
export async function backupAppStorage(transport: Transport): Promise<Buffer> {
|
|
51
|
+
const tracer = new LocalTracer("hw", {
|
|
52
|
+
transport: transport.getTraceContext(),
|
|
53
|
+
function: "backupAppStorage",
|
|
54
|
+
});
|
|
55
|
+
tracer.trace("Start");
|
|
56
|
+
|
|
57
|
+
const apdu: Readonly<APDU> = [...BACKUP_APP_STORAGE, Buffer.from([0x00])];
|
|
58
|
+
|
|
59
|
+
const response = await transport.send(...apdu, RESPONSE_STATUS_SET);
|
|
60
|
+
|
|
61
|
+
return parseResponse(response);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parses the response data buffer, check the status code and return the data.
|
|
66
|
+
*
|
|
67
|
+
* @param data - The response data buffer w/ status code.
|
|
68
|
+
* @returns The response data as a buffer w/o status code.
|
|
69
|
+
*/
|
|
70
|
+
export function parseResponse(data: Buffer): Buffer {
|
|
71
|
+
const tracer = new LocalTracer("hw", {
|
|
72
|
+
function: "parseResponse@backupAppStorage",
|
|
73
|
+
});
|
|
74
|
+
const status = data.readUInt16BE(data.length - 2);
|
|
75
|
+
tracer.trace("Result status from 0xe06b0000", { status });
|
|
76
|
+
|
|
77
|
+
switch (status) {
|
|
78
|
+
case StatusCodes.OK:
|
|
79
|
+
return data.subarray(0, data.length - 2);
|
|
80
|
+
case StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT:
|
|
81
|
+
throw new InvalidContext("Invalid context, getAppStorageInfo must be called.");
|
|
82
|
+
case StatusCodes.GEN_AES_KEY_FAILED:
|
|
83
|
+
throw new GenerateAesKeyFailed("Failed to generate AES key.");
|
|
84
|
+
case StatusCodes.INTERNAL_CRYPTO_OPERATION_FAILED:
|
|
85
|
+
throw new InternalCryptoOperationFailed("Internal error, crypto operation failed.");
|
|
86
|
+
case StatusCodes.INTERNAL_COMPUTE_AES_CMAC_FAILED:
|
|
87
|
+
throw new InternalComputeAesCmacFailed("Internal error, failed to compute AES CMAC.");
|
|
88
|
+
case StatusCodes.ENCRYPT_APP_STORAGE_FAILED:
|
|
89
|
+
throw new GenerateAesKeyFailed("Failed to encrypt the app storage backup.");
|
|
90
|
+
case StatusCodes.DEVICE_IN_RECOVERY_MODE:
|
|
91
|
+
break;
|
|
92
|
+
case StatusCodes.INVALID_BACKUP_STATE:
|
|
93
|
+
throw new InvalidBackupState("Invalid backup state, backup already performed.");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
throw new TransportStatusError(status);
|
|
97
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Transport, { StatusCodes } from "@ledgerhq/hw-transport";
|
|
2
|
+
import { getAppStorageInfo, parseResponse } from "./getAppStorageInfo";
|
|
3
|
+
import { AppStorageInfo } from "../../entities/AppStorageInfo";
|
|
4
|
+
import { InvalidAppNameLength } from "../../../errors";
|
|
5
|
+
|
|
6
|
+
jest.mock("@ledgerhq/hw-transport");
|
|
7
|
+
|
|
8
|
+
describe("getAppStorageInfo", () => {
|
|
9
|
+
let transport: Transport;
|
|
10
|
+
const response = Buffer.from([
|
|
11
|
+
0x00, 0x00, 0x04, 0xd2, 0x31, 0x2e, 0x30, 0x31, 0x01, 0x01, 0x68, 0x61, 0x73, 0x68, 0x68, 0x61,
|
|
12
|
+
0x73, 0x68, 0x31, 0x32, 0x33, 0x34, 0x68, 0x61, 0x73, 0x68, 0x68, 0x61, 0x73, 0x68, 0x68, 0x61,
|
|
13
|
+
0x73, 0x68, 0x68, 0x61, 0x73, 0x68, 0x68, 0x61, 0x73, 0x68, 0x90, 0x00,
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
transport = {
|
|
18
|
+
send: jest.fn().mockResolvedValue(response),
|
|
19
|
+
getTraceContext: jest.fn().mockResolvedValue(undefined),
|
|
20
|
+
} as unknown as Transport;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should call the send function with correct parameters", async () => {
|
|
28
|
+
const appName = "MyApp";
|
|
29
|
+
await getAppStorageInfo(transport, appName);
|
|
30
|
+
expect(transport.send).toHaveBeenCalledWith(
|
|
31
|
+
0xe0,
|
|
32
|
+
0x6a,
|
|
33
|
+
0x00,
|
|
34
|
+
0x00,
|
|
35
|
+
Buffer.from([0x05, 0x4d, 0x79, 0x41, 0x70, 0x70]),
|
|
36
|
+
[
|
|
37
|
+
StatusCodes.OK,
|
|
38
|
+
StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT,
|
|
39
|
+
StatusCodes.DEVICE_IN_RECOVERY_MODE,
|
|
40
|
+
StatusCodes.INVALID_APP_NAME_LENGTH,
|
|
41
|
+
],
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("parseResponse", () => {
|
|
46
|
+
it("should parse the response data correctly", () => {
|
|
47
|
+
const expected: AppStorageInfo = {
|
|
48
|
+
size: 1234,
|
|
49
|
+
dataVersion: "1.01",
|
|
50
|
+
hasSettings: true,
|
|
51
|
+
hasData: true,
|
|
52
|
+
hash: "hashhash1234hashhashhashhashhash",
|
|
53
|
+
};
|
|
54
|
+
expect(parseResponse(response)).toStrictEqual(expected);
|
|
55
|
+
});
|
|
56
|
+
it("should throw TransportStatusError if the response status is invalid", () => {
|
|
57
|
+
const data = Buffer.from([0x67, 0x0a]);
|
|
58
|
+
expect(() => parseResponse(data)).toThrow(
|
|
59
|
+
new InvalidAppNameLength("Invalid application name length, two chars minimum."),
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import Transport, { StatusCodes, TransportStatusError } from "@ledgerhq/hw-transport";
|
|
2
|
+
import { LocalTracer } from "@ledgerhq/logs";
|
|
3
|
+
import type { AppStorageInfo } from "../../entities/AppStorageInfo";
|
|
4
|
+
import type { APDU } from "../../entities/APDU";
|
|
5
|
+
import { AppNotFound, InvalidAppNameLength } from "../../../errors";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Name in documentation: INS_APP_STORAGE_GET_INFO
|
|
9
|
+
* cla: 0xe0
|
|
10
|
+
* ins: 0x6a
|
|
11
|
+
* p1: 0x00
|
|
12
|
+
* p2: 0x00
|
|
13
|
+
* data: APP_NAME_LEN (1 byte) + APP_NAME (variable) to configure at runtime
|
|
14
|
+
*/
|
|
15
|
+
const GET_APP_STORAGE_INFO = [0xe0, 0x6a, 0x00, 0x00] as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 0x9000: Success.
|
|
19
|
+
* 0x5123: Application not found.
|
|
20
|
+
* 0x662F: If the device is in recovery mode.
|
|
21
|
+
* 0x670A: Invalid application name length, two chars minimum.
|
|
22
|
+
*/
|
|
23
|
+
const RESPONSE_STATUS_SET: number[] = [
|
|
24
|
+
StatusCodes.OK,
|
|
25
|
+
StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT,
|
|
26
|
+
StatusCodes.DEVICE_IN_RECOVERY_MODE,
|
|
27
|
+
StatusCodes.INVALID_APP_NAME_LENGTH,
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Retrieves the application storage information from the device.
|
|
32
|
+
*
|
|
33
|
+
* @param transport - The transport object used to communicate with the device.
|
|
34
|
+
* @param appName - The name of the application to retrieve the storage information for.
|
|
35
|
+
* @returns A promise that resolves to the application storage information object.
|
|
36
|
+
* @throws {TransportStatusError} If the response status is invalid.
|
|
37
|
+
*/
|
|
38
|
+
export async function getAppStorageInfo(
|
|
39
|
+
transport: Transport,
|
|
40
|
+
appName: string,
|
|
41
|
+
): Promise<AppStorageInfo> {
|
|
42
|
+
const tracer = new LocalTracer("hw", {
|
|
43
|
+
transport: transport.getTraceContext(),
|
|
44
|
+
function: "getAppStorageInfo",
|
|
45
|
+
});
|
|
46
|
+
tracer.trace("Start");
|
|
47
|
+
|
|
48
|
+
const params: Buffer = Buffer.concat([
|
|
49
|
+
Buffer.from([appName.length]),
|
|
50
|
+
Buffer.from(appName, "ascii"),
|
|
51
|
+
]);
|
|
52
|
+
const apdu: Readonly<APDU> = [...GET_APP_STORAGE_INFO, params];
|
|
53
|
+
|
|
54
|
+
const response = await transport.send(...apdu, RESPONSE_STATUS_SET);
|
|
55
|
+
|
|
56
|
+
return parseResponse(response);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parses the response data from the device into a string.
|
|
61
|
+
*
|
|
62
|
+
* @param data - The response data received from the device.
|
|
63
|
+
* @returns A string representing the parsed response.
|
|
64
|
+
*/
|
|
65
|
+
export function parseResponse(data: Buffer): AppStorageInfo {
|
|
66
|
+
const tracer = new LocalTracer("hw", {
|
|
67
|
+
function: "parseResponse@getAppStorageInfo",
|
|
68
|
+
});
|
|
69
|
+
const status = data.readUInt16BE(data.length - 2);
|
|
70
|
+
tracer.trace("Result status from 0xe06a0000", { status });
|
|
71
|
+
|
|
72
|
+
switch (status) {
|
|
73
|
+
case StatusCodes.OK: {
|
|
74
|
+
/**
|
|
75
|
+
* The backup size is a 4-byte unsigned integer.
|
|
76
|
+
* The data version is a 4-byte string.
|
|
77
|
+
* The hasSettings and hasData flags are 1-byte booleans.
|
|
78
|
+
* The hash is a 32-byte string.
|
|
79
|
+
*/
|
|
80
|
+
let offset = 0;
|
|
81
|
+
const size = data.readUInt32BE(offset); // Len = 4
|
|
82
|
+
offset += 4;
|
|
83
|
+
const dataVersion = data.subarray(offset, offset + 4).toString(); // Len = 4
|
|
84
|
+
offset += 4;
|
|
85
|
+
const hasSettings = data.readUIntBE(offset, 1) === 1; // Len = 1
|
|
86
|
+
offset += 1;
|
|
87
|
+
const hasData = data.readUIntBE(offset, 1) === 1; // Len = 1
|
|
88
|
+
offset += 1;
|
|
89
|
+
const hash = data.subarray(offset, offset + 32).toString(); // Len = 32
|
|
90
|
+
|
|
91
|
+
return { size, dataVersion, hasSettings, hasData, hash };
|
|
92
|
+
}
|
|
93
|
+
case StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT:
|
|
94
|
+
throw new AppNotFound("Application not found.");
|
|
95
|
+
case StatusCodes.DEVICE_IN_RECOVERY_MODE:
|
|
96
|
+
break;
|
|
97
|
+
case StatusCodes.INVALID_APP_NAME_LENGTH:
|
|
98
|
+
throw new InvalidAppNameLength("Invalid application name length, two chars minimum.");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new TransportStatusError(status);
|
|
102
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import Transport, { StatusCodes } from "@ledgerhq/hw-transport";
|
|
2
|
+
import { restoreAppStorage, parseResponse } from "./restoreAppStorage";
|
|
3
|
+
import { InvalidRestoreState } from "../../../errors";
|
|
4
|
+
|
|
5
|
+
jest.mock("@ledgerhq/hw-transport");
|
|
6
|
+
|
|
7
|
+
describe("restoreAppStorage", () => {
|
|
8
|
+
let transport: Transport;
|
|
9
|
+
const response = Buffer.from([0x90, 0x00]);
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
transport = {
|
|
13
|
+
send: jest.fn().mockResolvedValue(response),
|
|
14
|
+
getTraceContext: jest.fn().mockResolvedValue(undefined),
|
|
15
|
+
} as unknown as Transport;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should call the send function with correct parameters", async () => {
|
|
23
|
+
const chunk = Buffer.from("106RueduTemple");
|
|
24
|
+
await restoreAppStorage(transport, chunk);
|
|
25
|
+
expect(transport.send).toHaveBeenCalledWith(
|
|
26
|
+
0xe0,
|
|
27
|
+
0x6d,
|
|
28
|
+
0x00,
|
|
29
|
+
0x00,
|
|
30
|
+
Buffer.from([
|
|
31
|
+
0x0e, 0x31, 0x30, 0x36, 0x52, 0x75, 0x65, 0x64, 0x75, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x65,
|
|
32
|
+
]),
|
|
33
|
+
[
|
|
34
|
+
StatusCodes.OK,
|
|
35
|
+
StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT,
|
|
36
|
+
StatusCodes.GEN_AES_KEY_FAILED,
|
|
37
|
+
StatusCodes.INTERNAL_CRYPTO_OPERATION_FAILED,
|
|
38
|
+
StatusCodes.DEVICE_IN_RECOVERY_MODE,
|
|
39
|
+
StatusCodes.INVALID_RESTORE_STATE,
|
|
40
|
+
StatusCodes.INVALID_CHUNK_LENGTH,
|
|
41
|
+
StatusCodes.INVALID_BACKUP_HEADER,
|
|
42
|
+
],
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("parseResponse", () => {
|
|
47
|
+
it("should parse the response data correctly", () => {
|
|
48
|
+
expect(() => parseResponse(response)).not.toThrow();
|
|
49
|
+
});
|
|
50
|
+
it("should throw TransportStatusError if the response status is invalid", () => {
|
|
51
|
+
const data = Buffer.from([0x66, 0x43]);
|
|
52
|
+
expect(() => parseResponse(data)).toThrow(
|
|
53
|
+
new InvalidRestoreState("Invalid restore state, restore already performed."),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import Transport, { StatusCodes, TransportStatusError } from "@ledgerhq/hw-transport";
|
|
2
|
+
import { LocalTracer } from "@ledgerhq/logs";
|
|
3
|
+
import type { APDU } from "../../entities/APDU";
|
|
4
|
+
import {
|
|
5
|
+
GenerateAesKeyFailed,
|
|
6
|
+
InternalCryptoOperationFailed,
|
|
7
|
+
InvalidBackupHeader,
|
|
8
|
+
InvalidChunkLength,
|
|
9
|
+
InvalidContext,
|
|
10
|
+
InvalidRestoreState,
|
|
11
|
+
} from "../../../errors";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Name in documentation: INS_APP_STORAGE_GET_INFO
|
|
15
|
+
* cla: 0xe0
|
|
16
|
+
* ins: 0x6d
|
|
17
|
+
* p1: 0x00
|
|
18
|
+
* p2: 0x00
|
|
19
|
+
* data: CHUNK_LEN + CHUNK to configure at runtime
|
|
20
|
+
*/
|
|
21
|
+
const RESTORE_APP_STORAGE = [0xe0, 0x6d, 0x00, 0x00] as const;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 0x9000: Success.
|
|
25
|
+
* 0x5123: Invalid context, Restore Init must be called first.
|
|
26
|
+
* 0x5419: Failed to generate AES key.
|
|
27
|
+
* 0x541A: Failed to decrypt the app storage backup.
|
|
28
|
+
* 0x662F: Invalid device state, recovery mode.
|
|
29
|
+
* 0x6643: Invalid restore state, restore already performed.
|
|
30
|
+
* 0x6734: Invalid CHUNK_LEN.
|
|
31
|
+
* 0x684A: Invalid backup, app storage header is not valid.
|
|
32
|
+
*/
|
|
33
|
+
const RESPONSE_STATUS_SET: number[] = [
|
|
34
|
+
StatusCodes.OK,
|
|
35
|
+
StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT,
|
|
36
|
+
StatusCodes.GEN_AES_KEY_FAILED,
|
|
37
|
+
StatusCodes.INTERNAL_CRYPTO_OPERATION_FAILED,
|
|
38
|
+
StatusCodes.DEVICE_IN_RECOVERY_MODE,
|
|
39
|
+
StatusCodes.INVALID_RESTORE_STATE,
|
|
40
|
+
StatusCodes.INVALID_CHUNK_LENGTH,
|
|
41
|
+
StatusCodes.INVALID_BACKUP_HEADER,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Restores the application storage.
|
|
46
|
+
*
|
|
47
|
+
* @param transport - The transport object used for communication with the device.
|
|
48
|
+
* @param chunk - The chunk of data to restore.
|
|
49
|
+
* @returns A promise that resolves to void.
|
|
50
|
+
*/
|
|
51
|
+
export async function restoreAppStorage(transport: Transport, chunk: Buffer): Promise<void> {
|
|
52
|
+
const tracer = new LocalTracer("hw", {
|
|
53
|
+
transport: transport.getTraceContext(),
|
|
54
|
+
function: "restoreAppStorage",
|
|
55
|
+
});
|
|
56
|
+
tracer.trace("Start");
|
|
57
|
+
|
|
58
|
+
const params = Buffer.concat([Buffer.from([chunk.length]), chunk]);
|
|
59
|
+
const apdu: Readonly<APDU> = [...RESTORE_APP_STORAGE, params];
|
|
60
|
+
|
|
61
|
+
const response = await transport.send(...apdu, RESPONSE_STATUS_SET);
|
|
62
|
+
|
|
63
|
+
parseResponse(response);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function parseResponse(data: Buffer): void {
|
|
67
|
+
const tracer = new LocalTracer("hw", {
|
|
68
|
+
function: "parseResponse@restoreAppStorage",
|
|
69
|
+
});
|
|
70
|
+
const status = data.readUInt16BE(data.length - 2);
|
|
71
|
+
tracer.trace("Result status from 0xe06d0000", { status });
|
|
72
|
+
|
|
73
|
+
switch (status) {
|
|
74
|
+
case StatusCodes.OK:
|
|
75
|
+
return;
|
|
76
|
+
case StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT:
|
|
77
|
+
throw new InvalidContext("Invalid context, restoreAppStorageInit must be called first.");
|
|
78
|
+
case StatusCodes.GEN_AES_KEY_FAILED:
|
|
79
|
+
throw new GenerateAesKeyFailed("Failed to generate AES key.");
|
|
80
|
+
case StatusCodes.INTERNAL_CRYPTO_OPERATION_FAILED:
|
|
81
|
+
throw new InternalCryptoOperationFailed("Failed to decrypt the app storage backup.");
|
|
82
|
+
case StatusCodes.DEVICE_IN_RECOVERY_MODE:
|
|
83
|
+
break;
|
|
84
|
+
case StatusCodes.INVALID_RESTORE_STATE:
|
|
85
|
+
throw new InvalidRestoreState("Invalid restore state, restore already performed.");
|
|
86
|
+
case StatusCodes.INVALID_CHUNK_LENGTH:
|
|
87
|
+
throw new InvalidChunkLength("Invalid chunk length.");
|
|
88
|
+
case StatusCodes.INVALID_BACKUP_HEADER:
|
|
89
|
+
throw new InvalidBackupHeader("Invalid backup, app storage header is not valid.");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
throw new TransportStatusError(status);
|
|
93
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import Transport, { StatusCodes } from "@ledgerhq/hw-transport";
|
|
2
|
+
import { restoreAppStorageCommit, parseResponse } from "./restoreAppStorageCommit";
|
|
3
|
+
import { InvalidChunkLength } from "../../../errors";
|
|
4
|
+
|
|
5
|
+
jest.mock("@ledgerhq/hw-transport");
|
|
6
|
+
|
|
7
|
+
describe("restoreAppStorageCommit", () => {
|
|
8
|
+
let transport: Transport;
|
|
9
|
+
const response = Buffer.from([0x90, 0x00]);
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
transport = {
|
|
13
|
+
send: jest.fn().mockResolvedValue(response),
|
|
14
|
+
getTraceContext: jest.fn().mockResolvedValue(undefined),
|
|
15
|
+
} as unknown as Transport;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should call the send function with correct parameters", async () => {
|
|
23
|
+
await restoreAppStorageCommit(transport);
|
|
24
|
+
expect(transport.send).toHaveBeenCalledWith(0xe0, 0x6e, 0x00, 0x00, Buffer.from([0x00]), [
|
|
25
|
+
StatusCodes.OK,
|
|
26
|
+
StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT,
|
|
27
|
+
StatusCodes.GEN_AES_KEY_FAILED,
|
|
28
|
+
StatusCodes.INTERNAL_COMPUTE_AES_CMAC_FAILED,
|
|
29
|
+
StatusCodes.DEVICE_IN_RECOVERY_MODE,
|
|
30
|
+
StatusCodes.INVALID_CHUNK_LENGTH,
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("parseResponse", () => {
|
|
35
|
+
it("should parse the response data correctly", () => {
|
|
36
|
+
expect(() => parseResponse(response)).not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
it("should throw TransportStatusError if the response status is invalid", () => {
|
|
39
|
+
const data = Buffer.from([0x67, 0x34]);
|
|
40
|
+
expect(() => parseResponse(data)).toThrow(
|
|
41
|
+
new InvalidChunkLength("Invalid size of the restored app storage."),
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import Transport, { StatusCodes, TransportStatusError } from "@ledgerhq/hw-transport";
|
|
2
|
+
import { LocalTracer } from "@ledgerhq/logs";
|
|
3
|
+
import type { APDU } from "../../entities/APDU";
|
|
4
|
+
import {
|
|
5
|
+
GenerateAesKeyFailed,
|
|
6
|
+
InternalComputeAesCmacFailed,
|
|
7
|
+
InvalidChunkLength,
|
|
8
|
+
InvalidContext,
|
|
9
|
+
} from "../../../errors";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Name in documentation: INS_APP_STORAGE_RESTORE_COMMIT
|
|
13
|
+
* cla: 0xe0
|
|
14
|
+
* ins: 0x6e
|
|
15
|
+
* p1: 0x00
|
|
16
|
+
* p2: 0x00
|
|
17
|
+
* lc: 0x00
|
|
18
|
+
*/
|
|
19
|
+
const RESTORE_APP_STORAGE_COMMIT = [0xe0, 0x6e, 0x00, 0x00] as const;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 0x9000: Success.
|
|
23
|
+
* 0x5123: Invalid context, Restore Init must be called first.
|
|
24
|
+
* 0x5419: Internal error, crypto operaiton failed.
|
|
25
|
+
* 0x541B: Failed to verify backup authenticity.
|
|
26
|
+
* 0x662F: Invalid device state, recovery mode.
|
|
27
|
+
* 0x6734: Invalid size of the restored app storage.
|
|
28
|
+
*/
|
|
29
|
+
const RESPONSE_STATUS_SET: number[] = [
|
|
30
|
+
StatusCodes.OK,
|
|
31
|
+
StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT,
|
|
32
|
+
StatusCodes.GEN_AES_KEY_FAILED,
|
|
33
|
+
StatusCodes.INTERNAL_COMPUTE_AES_CMAC_FAILED,
|
|
34
|
+
StatusCodes.DEVICE_IN_RECOVERY_MODE,
|
|
35
|
+
StatusCodes.INVALID_CHUNK_LENGTH,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Restores the application storage commit.
|
|
40
|
+
*
|
|
41
|
+
* @param transport - The transport object used for communication with the device.
|
|
42
|
+
* @returns A promise that resolves to void.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
export async function restoreAppStorageCommit(transport: Transport): Promise<void> {
|
|
46
|
+
const tracer = new LocalTracer("hw", {
|
|
47
|
+
transport: transport.getTraceContext(),
|
|
48
|
+
function: "restoreAppStorageCommit",
|
|
49
|
+
});
|
|
50
|
+
tracer.trace("Start");
|
|
51
|
+
|
|
52
|
+
const apdu: Readonly<APDU> = [...RESTORE_APP_STORAGE_COMMIT, Buffer.from([0x00])];
|
|
53
|
+
|
|
54
|
+
const response = await transport.send(...apdu, RESPONSE_STATUS_SET);
|
|
55
|
+
|
|
56
|
+
parseResponse(response);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function parseResponse(data: Buffer): void {
|
|
60
|
+
const tracer = new LocalTracer("hw", {
|
|
61
|
+
function: "parseResponse@restoreAppStorageCommit",
|
|
62
|
+
});
|
|
63
|
+
const status = data.readUInt16BE(data.length - 2);
|
|
64
|
+
tracer.trace("Result status from 0xe06e0000", { status });
|
|
65
|
+
|
|
66
|
+
switch (status) {
|
|
67
|
+
case StatusCodes.OK:
|
|
68
|
+
return;
|
|
69
|
+
case StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT:
|
|
70
|
+
throw new InvalidContext("Invalid context, restoreAppStorageInit must be called first.");
|
|
71
|
+
case StatusCodes.GEN_AES_KEY_FAILED:
|
|
72
|
+
throw new GenerateAesKeyFailed("Internal error, crypto operation failed.");
|
|
73
|
+
case StatusCodes.INTERNAL_COMPUTE_AES_CMAC_FAILED:
|
|
74
|
+
throw new InternalComputeAesCmacFailed("Failed to verify backup authenticity.");
|
|
75
|
+
case StatusCodes.DEVICE_IN_RECOVERY_MODE:
|
|
76
|
+
break;
|
|
77
|
+
case StatusCodes.INVALID_CHUNK_LENGTH:
|
|
78
|
+
throw new InvalidChunkLength("Invalid size of the restored app storage.");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
throw new TransportStatusError(status);
|
|
82
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Transport, { StatusCodes } from "@ledgerhq/hw-transport";
|
|
2
|
+
import { restoreAppStorageInit, parseResponse } from "./restoreAppStorageInit";
|
|
3
|
+
import { InvalidAppNameLength } from "../../../errors";
|
|
4
|
+
|
|
5
|
+
jest.mock("@ledgerhq/hw-transport");
|
|
6
|
+
|
|
7
|
+
describe("restoreAppStorageInit", () => {
|
|
8
|
+
let transport: Transport;
|
|
9
|
+
const response = Buffer.from([0x90, 0x00]);
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
transport = {
|
|
13
|
+
send: jest.fn().mockResolvedValue(response),
|
|
14
|
+
getTraceContext: jest.fn().mockResolvedValue(undefined),
|
|
15
|
+
} as unknown as Transport;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should call the send function with correct parameters", async () => {
|
|
23
|
+
const appName = "MyApp";
|
|
24
|
+
const backupSize = 1234;
|
|
25
|
+
await restoreAppStorageInit(transport, appName, backupSize);
|
|
26
|
+
expect(transport.send).toHaveBeenCalledWith(
|
|
27
|
+
0xe0,
|
|
28
|
+
0x6c,
|
|
29
|
+
0x00,
|
|
30
|
+
0x00,
|
|
31
|
+
Buffer.from([0x09, 0x00, 0x00, 0x04, 0xd2, 0x4d, 0x79, 0x41, 0x70, 0x70]),
|
|
32
|
+
[
|
|
33
|
+
StatusCodes.OK,
|
|
34
|
+
StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT,
|
|
35
|
+
StatusCodes.DEVICE_IN_RECOVERY_MODE,
|
|
36
|
+
StatusCodes.USER_REFUSED_ON_DEVICE,
|
|
37
|
+
StatusCodes.PIN_NOT_SET,
|
|
38
|
+
StatusCodes.INVALID_APP_NAME_LENGTH,
|
|
39
|
+
StatusCodes.INVALID_BACKUP_LENGTH,
|
|
40
|
+
],
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("parseResponse", () => {
|
|
45
|
+
it("should parse the response data correctly", () => {
|
|
46
|
+
expect(() => parseResponse(response)).not.toThrow();
|
|
47
|
+
});
|
|
48
|
+
it("should throw TransportStatusError if the response status is invalid", () => {
|
|
49
|
+
const data = Buffer.from([0x67, 0x0a]);
|
|
50
|
+
expect(() => parseResponse(data)).toThrow(
|
|
51
|
+
new InvalidAppNameLength("Invalid application name length, two chars minimum."),
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import Transport, { StatusCodes, TransportStatusError } from "@ledgerhq/hw-transport";
|
|
2
|
+
import { LocalTracer } from "@ledgerhq/logs";
|
|
3
|
+
import type { APDU } from "../../entities/APDU";
|
|
4
|
+
import { AppNotFound, InvalidAppNameLength, InvalidBackupLength, PinNotSet } from "../../../errors";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Name in documentation: INS_APP_STORAGE_RESTORE_INIT
|
|
8
|
+
* cla: 0xe0
|
|
9
|
+
* ins: 0x6c
|
|
10
|
+
* p1: 0x00
|
|
11
|
+
* p2: 0x00
|
|
12
|
+
* data:
|
|
13
|
+
* - LC: BACKUP_LEN_LEN (=0x04) + APP_NAME_LEN (1 byte)
|
|
14
|
+
* - DATA: BACKUP_LEN + APP_NAME
|
|
15
|
+
*
|
|
16
|
+
* For example, the 'bitcoin' app with backup of length 0x00007000:
|
|
17
|
+
* 1. LC is 0x0b
|
|
18
|
+
* 2. DATA is 0x00007000 0x626974636f696e
|
|
19
|
+
*/
|
|
20
|
+
const RESTORE_APP_STORAGE_INIT = [0xe0, 0x6c, 0x00, 0x00] as const;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 0x9000: Success.
|
|
24
|
+
* 0x5123: Application not found.
|
|
25
|
+
* 0x662F: Invalid device state, recovery mode.
|
|
26
|
+
* 0x5501: Invalid consent, user rejected.
|
|
27
|
+
* 0x5502: Invalid consent, pin is not set.
|
|
28
|
+
* 0x670A: Invalid application name length, two chars minimum.
|
|
29
|
+
* 0x6733: Invalid BACKUP_LEN value.
|
|
30
|
+
*/
|
|
31
|
+
const RESPONSE_STATUS_SET: number[] = [
|
|
32
|
+
StatusCodes.OK,
|
|
33
|
+
StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT,
|
|
34
|
+
StatusCodes.DEVICE_IN_RECOVERY_MODE,
|
|
35
|
+
StatusCodes.USER_REFUSED_ON_DEVICE,
|
|
36
|
+
StatusCodes.PIN_NOT_SET,
|
|
37
|
+
StatusCodes.INVALID_APP_NAME_LENGTH,
|
|
38
|
+
StatusCodes.INVALID_BACKUP_LENGTH,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Restores the application storage initialization.
|
|
43
|
+
*
|
|
44
|
+
* @param transport - The transport object used for communication with the device.
|
|
45
|
+
* @param appName - The name of the application to restore the storage for.
|
|
46
|
+
* @param backupSize - The size of the backup to restore.
|
|
47
|
+
* @returns A promise that resolves to void.
|
|
48
|
+
*/
|
|
49
|
+
export async function restoreAppStorageInit(
|
|
50
|
+
transport: Transport,
|
|
51
|
+
appName: string,
|
|
52
|
+
backupSize: number,
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
const tracer = new LocalTracer("hw", {
|
|
55
|
+
transport: transport.getTraceContext(),
|
|
56
|
+
function: "restoreAppStorageInit",
|
|
57
|
+
});
|
|
58
|
+
tracer.trace("Start");
|
|
59
|
+
|
|
60
|
+
const params: Buffer = Buffer.concat([
|
|
61
|
+
Buffer.from([appName.length + 4]), // LC
|
|
62
|
+
Buffer.from(backupSize.toString(16).padStart(8, "0"), "hex"), // BACKUP_LEN
|
|
63
|
+
Buffer.from(appName, "ascii"), // APP_NAME
|
|
64
|
+
]);
|
|
65
|
+
const apdu: Readonly<APDU> = [...RESTORE_APP_STORAGE_INIT, params];
|
|
66
|
+
|
|
67
|
+
const response = await transport.send(...apdu, RESPONSE_STATUS_SET);
|
|
68
|
+
|
|
69
|
+
parseResponse(response);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function parseResponse(data: Buffer): void {
|
|
73
|
+
const tracer = new LocalTracer("hw", {
|
|
74
|
+
function: "parseResponse@restoreAppStorageInit",
|
|
75
|
+
});
|
|
76
|
+
const status = data.readUInt16BE(data.length - 2);
|
|
77
|
+
tracer.trace("Result status from 0xe06c0000", { status });
|
|
78
|
+
|
|
79
|
+
switch (status) {
|
|
80
|
+
case StatusCodes.OK:
|
|
81
|
+
return;
|
|
82
|
+
case StatusCodes.APP_NOT_FOUND_OR_INVALID_CONTEXT:
|
|
83
|
+
throw new AppNotFound("Application not found.");
|
|
84
|
+
case StatusCodes.DEVICE_IN_RECOVERY_MODE:
|
|
85
|
+
case StatusCodes.USER_REFUSED_ON_DEVICE:
|
|
86
|
+
break;
|
|
87
|
+
case StatusCodes.PIN_NOT_SET:
|
|
88
|
+
throw new PinNotSet("Invalid consent, PIN is not set.");
|
|
89
|
+
case StatusCodes.INVALID_APP_NAME_LENGTH:
|
|
90
|
+
throw new InvalidAppNameLength("Invalid application name length, two chars minimum.");
|
|
91
|
+
case StatusCodes.INVALID_BACKUP_LENGTH:
|
|
92
|
+
throw new InvalidBackupLength("Invalid backup length.");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new TransportStatusError(status);
|
|
96
|
+
}
|