#include "shared_context.h" #include "apdu_constants.h" #include "ui_flow.h" #include "tokens.h" #include "eth_plugin_interface.h" #include "eth_plugin_internal.h" // Supported internal plugins #define ERC721_STR "ERC721" #define ERC1155_STR "ERC1155" #define TYPE_SIZE 1 #define VERSION_SIZE 1 #define PLUGIN_NAME_LENGTH_SIZE 1 #define CHAIN_ID_SIZE 8 #define KEY_ID_SIZE 1 #define ALGORITHM_ID_SIZE 1 #define SIGNATURE_LENGTH_SIZE 1 #define HEADER_SIZE TYPE_SIZE + VERSION_SIZE + PLUGIN_NAME_LENGTH_SIZE #define MIN_DER_SIG_SIZE 67 #define MAX_DER_SIG_SIZE 72 typedef enum Type { ETH_PLUGIN = 0x01, } Type; typedef enum Version { VERSION_1 = 0x01, } Version; typedef enum KeyId { TEST_KEY = 0x00, PERSO_V2_KEY_1 = 0x01, // Must ONLY be used with ERC721 and ERC1155 plugin AWS_PLUGIN_KEY_1 = 0x02, } KeyId; // Algorithm Id consists of a Key spec and an algorithm spec. // Format is: KEYSPEC__ALGOSPEC typedef enum AlgorithmID { ECC_SECG_P256K1__ECDSA_SHA_256 = 0x01, } AlgorithmID; #ifdef HAVE_NFT_TESTING_KEY static const uint8_t LEDGER_TESTING_KEY[] = { 0x04, 0xf5, 0x70, 0x0c, 0xa1, 0xe8, 0x74, 0x24, 0xc7, 0xc7, 0xd1, 0x19, 0xe7, 0xe3, 0xc1, 0x89, 0xb1, 0x62, 0x50, 0x94, 0xdb, 0x6e, 0xa0, 0x40, 0x87, 0xc8, 0x30, 0x00, 0x7d, 0x0b, 0x46, 0x9a, 0x53, 0x11, 0xee, 0x6a, 0x1a, 0xcd, 0x1d, 0xa5, 0xaa, 0xb0, 0xf5, 0xc6, 0xdf, 0x13, 0x15, 0x8d, 0x28, 0xcc, 0x12, 0xd1, 0xdd, 0xa6, 0xec, 0xe9, 0x46, 0xb8, 0x9d, 0x5c, 0x05, 0x49, 0x92, 0x59, 0xc4}; #endif static const uint8_t LEDGER_PERSO_V2_PUBLIC_KEY[] = {}; // Only used for signing NFT plugins (ERC721 and ERC1155) static const uint8_t LEDGER_NFT_SELECTOR_PUBLIC_KEY[] = {}; // Verification function used to verify the signature typedef bool verificationAlgo(const cx_ecfp_public_key_t *, int, cx_md_t, const unsigned char *, unsigned int, unsigned char *, unsigned int); // Returns the plugin type of a given plugin name. // If the plugin name is not a specific known internal plugin, this function default return value is // `EXERNAL`. static pluginType_t getPluginType(char *pluginName, uint8_t pluginNameLength) { if (pluginNameLength == sizeof(ERC721_STR) - 1 && strncmp(pluginName, ERC721_STR, pluginNameLength) == 0) { return ERC721; } else if (pluginNameLength == sizeof(ERC1155_STR) - 1 && strncmp(pluginName, ERC1155_STR, pluginNameLength) == 0) { return ERC1155; } else { return EXTERNAL; } } void handleSetPlugin(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx) { UNUSED(p1); UNUSED(p2); UNUSED(flags); PRINTF("Handling set Plugin\n"); uint8_t hash[INT256_LENGTH] = {0}; cx_ecfp_public_key_t pluginKey = {0}; tokenContext_t *tokenContext = &dataContext.tokenContext; uint8_t offset = 0; if (dataLength <= HEADER_SIZE) { PRINTF("Data too small for headers: expected at least %d, got %d\n", HEADER_SIZE, dataLength); THROW(0x6A80); } enum Type type = workBuffer[offset]; PRINTF("Type: %d\n", type); switch (type) { case ETH_PLUGIN: break; default: PRINTF("Unsupported type %d\n", type); THROW(0x6a80); break; } offset += TYPE_SIZE; uint8_t version = workBuffer[offset]; PRINTF("version: %d\n", version); switch (version) { case VERSION_1: break; default: PRINTF("Unsupported version %d\n", version); THROW(0x6a80); break; } offset += VERSION_SIZE; uint8_t pluginNameLength = workBuffer[offset]; offset += PLUGIN_NAME_LENGTH_SIZE; // Size of the payload (everything except the signature) uint8_t payloadSize = HEADER_SIZE + pluginNameLength + ADDRESS_LENGTH + SELECTOR_SIZE + CHAIN_ID_SIZE + KEY_ID_SIZE + ALGORITHM_ID_SIZE; if (dataLength < payloadSize) { PRINTF("Data too small for payload: expected at least %d, got %d\n", payloadSize, dataLength); THROW(0x6A80); } // `+ 1` because we want to add a null terminating character. if (pluginNameLength + 1 > sizeof(tokenContext->pluginName)) { PRINTF("plugin name too big: expected max %d, got %d\n", sizeof(dataContext.tokenContext.pluginName), pluginNameLength + 1); THROW(0x6A80); } // Safe because we've checked the size before. memcpy(tokenContext->pluginName, workBuffer + offset, pluginNameLength); tokenContext->pluginName[pluginNameLength] = '\0'; PRINTF("Length: %d\n", pluginNameLength); PRINTF("plugin name: %s\n", tokenContext->pluginName); offset += pluginNameLength; memcpy(tokenContext->contractAddress, workBuffer + offset, ADDRESS_LENGTH); PRINTF("Address: %.*H\n", ADDRESS_LENGTH, workBuffer + offset); offset += ADDRESS_LENGTH; memcpy(tokenContext->methodSelector, workBuffer + offset, SELECTOR_SIZE); PRINTF("Selector: %.*H\n", SELECTOR_SIZE, tokenContext->methodSelector); offset += SELECTOR_SIZE; // TODO: store chainID and assert that tx is using the same chainid. // uint64_t chainid = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE); // PRINTF("ChainID: %.*H\n", sizeof(chainid), &chainid); offset += CHAIN_ID_SIZE; enum KeyId keyId = workBuffer[offset]; uint8_t const *rawKey; uint8_t rawKeyLen; PRINTF("KeyID: %d\n", keyId); switch (keyId) { #ifdef HAVE_NFT_TESTING_KEY case TEST_KEY: rawKey = LEDGER_TESTING_KEY; rawKeyLen = sizeof(LEDGER_TESTING_KEY); break; #endif case PERSO_V2_KEY_1: rawKey = LEDGER_PERSO_V2_PUBLIC_KEY; rawKeyLen = sizeof(LEDGER_PERSO_V2_PUBLIC_KEY); break; case AWS_PLUGIN_KEY_1: rawKey = LEDGER_NFT_SELECTOR_PUBLIC_KEY; rawKeyLen = sizeof(LEDGER_NFT_SELECTOR_PUBLIC_KEY); break; default: PRINTF("KeyID %d not supported\n", keyId); THROW(0x6A80); break; } PRINTF("RawKey: %.*H\n", rawKeyLen, rawKey); offset += KEY_ID_SIZE; uint8_t algorithmId = workBuffer[offset]; PRINTF("Algorithm: %d\n", algorithmId); cx_curve_t curve; verificationAlgo *verificationFn; cx_md_t hashId; switch (algorithmId) { case ECC_SECG_P256K1__ECDSA_SHA_256: curve = CX_CURVE_256K1; verificationFn = (verificationAlgo*)cx_ecdsa_verify; hashId = CX_SHA256; break; default: PRINTF("Incorrect algorithmId %d\n", algorithmId); THROW(0x6a80); break; } offset += ALGORITHM_ID_SIZE; PRINTF("hashing: %.*H\n", payloadSize, workBuffer); cx_hash_sha256(workBuffer, payloadSize, hash, sizeof(hash)); if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE) { PRINTF("Data too short to hold signature length\n"); THROW(0x6a80); } uint8_t signatureLen = workBuffer[offset]; PRINTF("Sigature len: %d\n", signatureLen); if (signatureLen < MIN_DER_SIG_SIZE || signatureLen > MAX_DER_SIG_SIZE) { PRINTF("SignatureLen too big or too small. Must be between %d and %d, got %d\n", MIN_DER_SIG_SIZE, MAX_DER_SIG_SIZE, signatureLen); THROW(0x6a80); } offset += SIGNATURE_LENGTH_SIZE; if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE + signatureLen) { PRINTF("Signature could not fit in data\n"); THROW(0x6a80); } cx_ecfp_init_public_key(curve, rawKey, rawKeyLen, &pluginKey); if (!verificationFn(&pluginKey, CX_LAST, hashId, hash, sizeof(hash), workBuffer + offset, signatureLen)) { #ifndef HAVE_BYPASS_SIGNATURES PRINTF("Invalid NFT signature\n"); THROW(0x6A80); #endif } pluginType = getPluginType(tokenContext->pluginName, pluginNameLength); if (keyId == AWS_PLUGIN_KEY_1) { if (pluginType != ERC721 && pluginType != ERC1155) { PRINTF("AWS key must only be used to set NFT internal plugins\n"); THROW(0x6A80); } } switch (pluginType) { case EXTERNAL: { PRINTF("Check external plugin %s\n", tokenContext->pluginName); // Check if the plugin is present on the device uint32_t params[2]; params[0] = (uint32_t) tokenContext->pluginName; params[1] = ETH_PLUGIN_CHECK_PRESENCE; BEGIN_TRY { TRY { os_lib_call(params); } CATCH_OTHER(e) { PRINTF("%s external plugin is not present\n", tokenContext->pluginName); memset(tokenContext->pluginName, 0, sizeof(tokenContext->pluginName)); THROW(0x6984); } FINALLY { } } END_TRY; break; } default: break; } G_io_apdu_buffer[(*tx)++] = 0x90; G_io_apdu_buffer[(*tx)++] = 0x00; }