@ledgerhq/domain-service
Version:
Ledger service responsible for domain handling
85 lines (74 loc) • 3.45 kB
text/typescript
/* istanbul ignore file: this file can be ignored in tests as long as it's only for debugging purpose & not touching userland */
/**
* Helper designed to parse the APDU crafted by the Ledger backend to clear sign domains.
* The APDU is encoded with a TLV scheme: https://en.wikipedia.org/wiki/Type%E2%80%93length%E2%80%93value
* This is not used in our implementation per say, use this purely as a debug tool.
*/
// List of TLV possible based on the `Nano Trusted Names Descriptor Format & APIs` architecture on our confluence.
// Sorry about that open source people.
type TLV_TYPES =
| "STRUCTURE_TYPE"
| "VERSION"
| "CHALLENGE"
| "SIGNER_KEY_ID"
| "SIGNER_ALGO"
| "TRUSTED_NAME"
| "COIN_TYPE"
| "ADDRESS"
| "SIGNATURE";
type TLV_IDS = "01" | "02" | "12" | "13" | "14" | "15" | "20" | "21" | "22";
const TLVs: Record<TLV_IDS, { typeName: TLV_TYPES; parser: (input: string) => string | number }> = {
"01": { typeName: "STRUCTURE_TYPE", parser: input => parseInt(input, 16) },
"02": { typeName: "VERSION", parser: input => parseInt(input, 16) },
"12": { typeName: "CHALLENGE", parser: input => input },
"13": { typeName: "SIGNER_KEY_ID", parser: input => input },
"14": { typeName: "SIGNER_ALGO", parser: input => input },
"20": {
typeName: "TRUSTED_NAME",
parser: input => Buffer.from(input, "hex").toString(),
},
"21": { typeName: "COIN_TYPE", parser: input => parseInt(input, 16) },
"22": { typeName: "ADDRESS", parser: input => `0x${input}` },
"15": { typeName: "SIGNATURE", parser: input => input },
};
/**
* Parser logic
*/
export const tlvParser = (
apdu: string,
): { T: TLV_TYPES; L: string; V: string | number | null }[] => {
const parsedApdu: { T: TLV_TYPES; L: string; V: string | number | null }[] = [];
let apduLeft = apdu;
while (apduLeft.length > 0) {
const type = apduLeft.substring(0, 2) as TLV_IDS;
const TLV = TLVs[type] || {};
const lengthByte = apduLeft.substring(2, 4);
// documentation to explain DER integer encoding
// @see https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-integer
const isLengthDEREncodedInt = !!(parseInt(lengthByte, 16) >> 7); // check if the high bit of the 7 bits number a 1 ?
if (isLengthDEREncodedInt) {
const lengthByteLast6Bits = parseInt(lengthByte, 16) & 0x3f; // 0x3F === "111111" so this will return the last 6 bits of the DER encoded integer
const lengthSizeInBytes = parseInt(lengthByteLast6Bits.toString(16), 10); // length size in bytes as decimal
const offset = 4 + lengthSizeInBytes * 2; // we'll have to take the number of bytes for the size into account to not consider it as part of the Value of th TLV
const length = parseInt(apduLeft.substring(4, offset), 16); // the actual length of the Value
const lengthInChars = length * 2;
const value = apduLeft.substring(offset, offset + lengthInChars);
parsedApdu.push({
T: TLV.typeName,
L: length.toString(16),
V: TLV.parser?.(value) || null,
});
apduLeft = apduLeft.substring(offset + lengthInChars);
} else {
const lengthInChars = parseInt(lengthByte, 16) * 2;
const value = apduLeft.substring(4, 4 + lengthInChars);
parsedApdu.push({
T: TLV.typeName,
L: lengthByte,
V: TLV.parser?.(value) || null,
});
apduLeft = apduLeft.substring(4 + lengthInChars);
}
}
return parsedApdu;
};