diff --git a/Makefile.app_params b/Makefile.app_params new file mode 100644 index 000000000..be6b3b4a6 --- /dev/null +++ b/Makefile.app_params @@ -0,0 +1,126 @@ +#******************************************************************************* +# Ledger SDK +# (c) 2023 Ledger +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#******************************************************************************* + +# Command to print ICONNAME hexadecimal bitmap on stdout according to the +# hardware target. +ifneq ($(TARGET),nanos) +#inverse B&W for non Stax +ifneq ($(TARGET_NAME),TARGET_STAX) +ICONHEX_CMD=python3 $(BOLOS_SDK)/lib_nbgl/tools/icon2glyph.py --reverse --hexbitmaponly $(ICONNAME) +else +ICONHEX_CMD=python3 $(BOLOS_SDK)/lib_nbgl/tools/icon2glyph.py --hexbitmaponly $(ICONNAME) +endif +else +ICONHEX_CMD=python3 $(BOLOS_SDK)/icon3.py --hexbitmaponly $(ICONNAME) +endif + + +######################################### +# Parse APP_LOAD_PARAMS # +######################################### +# This is necessary when makefile.standard_app is not completely used. +# Correctly implemented apps should not set anything in APP_LOAD_PARAMS anymore +# Potential presents info are: +# --appFlags +# --curve +# --path +# --path_slip21 +# --tlvraw +# --dep +# --nocrc +# Other info are considered an error and will be silently discarded. + +ifneq ($(APP_LOAD_PARAMS),) + EXTRACTED_APP_FLAGS := $(shell python3 $(BOLOS_SDK)/extract_param.py --appFlags $(APP_LOAD_PARAMS)) + APP_FLAGS_APP_LOAD_PARAMS += $(EXTRACTED_APP_FLAGS) + + EXTRACTED_CURVE := $(shell python3 $(BOLOS_SDK)/extract_param.py --curve $(APP_LOAD_PARAMS)) + CURVE_APP_LOAD_PARAMS += $(EXTRACTED_CURVE) + + EXTRACTED_PATH := $(shell python3 $(BOLOS_SDK)/extract_param.py --path $(APP_LOAD_PARAMS)) + PATH_APP_LOAD_PARAMS += $(EXTRACTED_PATH) + + EXTRACTED_PATH_SLIP21 := $(shell python3 $(BOLOS_SDK)/extract_param.py --path_slip21 $(APP_LOAD_PARAMS)) + PATH_SLIP21_APP_LOAD_PARAMS += $(EXTRACTED_PATH_SLIP21) + + EXTRACTED_TLVRAW := $(shell python3 $(BOLOS_SDK)/extract_param.py --tlvraw $(APP_LOAD_PARAMS)) + TLVRAW_APP_LOAD_PARAMS += $(EXTRACTED_TLVRAW) + + EXTRACTED_DEP := $(shell python3 $(BOLOS_SDK)/extract_param.py --dep $(APP_LOAD_PARAMS)) + DEP_APP_LOAD_PARAMS += $(EXTRACTED_DEP) + + ifneq ($(findstring --nocrc,$(APP_LOAD_PARAMS)),) + ENABLE_NOCRC_APP_LOAD_PARAMS = 1 + endif +endif + + +######################################### +# Generate install_params # +######################################### +# Compute params to call install_params.py +# Consider only one path_slip21 can be added, whereas LedgerBlue seems to +# support multiple, but has the path can hold a " " in it, it mess with the +# foreach, so we choose to restrict to only one path_slip21. +APP_INSTALL_PARAMS = --appName $(APPNAME) +APP_INSTALL_PARAMS += --appVersion $(APPVERSION) +APP_INSTALL_PARAMS += `ICONHEX=\`$(ICONHEX_CMD) 2>/dev/null\` ; [ ! -z "$$ICONHEX" ] && echo "--icon $$ICONHEX"` +APP_INSTALL_PARAMS += $(foreach curve, $(CURVE_APP_LOAD_PARAMS), --curve $(curve)) +APP_INSTALL_PARAMS += $(foreach path, $(PATH_APP_LOAD_PARAMS), --path $(path)) +ifneq ($(PATH_SLIP21_APP_LOAD_PARAMS),) + APP_INSTALL_PARAMS += --path_slip21 $(PATH_SLIP21_APP_LOAD_PARAMS) +endif +APP_INSTALL_PARAMS += $(foreach tlvraw, $(TLVRAW_APP_LOAD_PARAMS), --tlvraw $(tlvraw)) +APP_INSTALL_PARAMS += $(foreach dep, $(DEP_APP_LOAD_PARAMS), --dep $(dep)) + +# Compute install_params tlv binary blob then expose it via a define to +# src/app_metadata.c so that it is inserted in the binary at link time +APP_INSTALL_PARAMS_DATA := $(shell python3 $(BOLOS_SDK)/install_params.py $(APP_INSTALL_PARAMS)) +DEFINES += APP_INSTALL_PARAMS_DATA=$(APP_INSTALL_PARAMS_DATA) + +######################################### +# Generate APP_LOAD_PARAMS # +######################################### +# Rewrite APP_LOAD_PARAMS with params needed for generating the sideloading +# APDUs. +# This variable is then used in some Makefiles target as Ledgerblue.loadapp +# script parameters. +APP_LOAD_PARAMS = --targetId $(TARGET_ID) +APP_LOAD_PARAMS += --targetVersion="$(TARGET_VERSION)" +APP_LOAD_PARAMS += --apiLevel $(API_LEVEL) +APP_LOAD_PARAMS += --fileName bin/app.hex +APP_LOAD_PARAMS += --appName $(APPNAME) +ifneq ($(APP_FLAGS_APP_LOAD_PARAMS),) + APP_LOAD_PARAMS += --appFlags $(APP_FLAGS_APP_LOAD_PARAMS) +endif +APP_LOAD_PARAMS += --delete +APP_LOAD_PARAMS += --tlv +APP_LOAD_PARAMS += --dataSize $$((0x`cat debug/app.map | grep _envram_data | tr -s ' ' | cut -f2 -d' ' |cut -f2 -d'x' ` - 0x`cat debug/app.map | grep _nvram_data | tr -s ' ' | cut -f2 -d' ' | cut -f2 -d'x'`)) +APP_LOAD_PARAMS += --installparamsSize $$((0x`cat debug/app.map | grep _einstall_parameters | tr -s ' ' | cut -f2 -d' ' |cut -f2 -d'x'` - 0x`cat debug/app.map | grep _install_parameters | tr -s ' ' | cut -f2 -d' ' |cut -f2 -d'x'`)) + +ifeq ($(ENABLE_NOCRC_APP_LOAD_PARAMS), 1) + APP_LOAD_PARAMS += --nocrc +endif + +COMMON_DELETE_PARAMS = --targetId $(TARGET_ID) --appName $(APPNAME) + +# Extra load parameters for loadApp script +ifneq ($(SCP_PRIVKEY),) + PARAM_SCP += --rootPrivateKey $(SCP_PRIVKEY) + APP_LOAD_PARAMS += $(PARAM_SCP) + COMMON_DELETE_PARAMS += $(PARAM_SCP) +endif diff --git a/Makefile.defines b/Makefile.defines index 0b7761d3b..72a2c12fd 100644 --- a/Makefile.defines +++ b/Makefile.defines @@ -60,26 +60,6 @@ DEFINES += API_LEVEL=$(API_LEVEL) APP_METADATA_LIST := TARGET TARGET_NAME APPVERSION SDK_NAME SDK_VERSION SDK_HASH DEFINES += $(foreach item,$(APP_METADATA_LIST), $(item)=\"$($(item))\") -# extra load parameters for loadApp script -ifneq ($(SCP_PRIVKEY),) -PARAM_SCP+=--rootPrivateKey $(SCP_PRIVKEY) -endif - -# Command to print ICONNAME hexadecimal bitmap on stdout -# according to the hardware target. -ifneq ($(TARGET),nanos) -#inverse B&W for non Stax -ifneq ($(TARGET_NAME),TARGET_STAX) -ICONHEX_CMD=python3 $(BOLOS_SDK)/lib_nbgl/tools/icon2glyph.py --reverse --hexbitmaponly $(ICONNAME) -else -ICONHEX_CMD=python3 $(BOLOS_SDK)/lib_nbgl/tools/icon2glyph.py --hexbitmaponly $(ICONNAME) -endif -else -ICONHEX_CMD=python3 $(BOLOS_SDK)/icon3.py --hexbitmaponly $(ICONNAME) -endif - -COMMON_LOAD_PARAMS=--tlv --targetId $(TARGET_ID) --targetVersion="$(TARGET_VERSION)" --apiLevel $(API_LEVEL) --delete --fileName bin/app.hex --appName $(APPNAME) --appVersion $(APPVERSION) --dataSize $$((0x`cat debug/app.map |grep _envram_data | tr -s ' ' | cut -f2 -d' '|cut -f2 -d'x'` - 0x`cat debug/app.map |grep _nvram_data | tr -s ' ' | cut -f2 -d' '|cut -f2 -d'x'`)) `ICONHEX=\`$(ICONHEX_CMD) 2>/dev/null\` ; [ ! -z "$$ICONHEX" ] && echo "--icon $$ICONHEX"` $(PARAM_SCP) -COMMON_DELETE_PARAMS=--targetId $(TARGET_ID) --appName $(APPNAME) $(PARAM_SCP) BUILD_DIR := build TARGET_BUILD_DIR := $(BUILD_DIR)/$(TARGET) diff --git a/Makefile.rules_generic b/Makefile.rules_generic index f074367d8..584fe343e 100644 --- a/Makefile.rules_generic +++ b/Makefile.rules_generic @@ -15,6 +15,10 @@ # limitations under the License. #******************************************************************************* +# Include Makefile.app_params here so that it is: +# - included on all apps Makefile, without changing them +# - included after apps Makefile changes on APP_LOAD_PARAMS +include $(BOLOS_SDK)/Makefile.app_params # consider every intermediate target as final to avoid deleting intermediate files .SECONDARY: diff --git a/Makefile.standard_app b/Makefile.standard_app index 950ea792d..461f3f08f 100644 --- a/Makefile.standard_app +++ b/Makefile.standard_app @@ -165,22 +165,14 @@ ifeq ($(HAVE_APPLICATION_FLAG_LIBRARY), 1) STANDARD_APP_FLAGS := $(shell echo $$(($(STANDARD_APP_FLAGS) + 0x800))) endif -APP_FLAGS = $(shell printf '0x%x' $$(( $(STANDARD_APP_FLAGS) + $(CUSTOM_APP_FLAGS) )) ) - -APP_LOAD_PARAMS += --appFlags $(APP_FLAGS) - -APP_LOAD_PARAMS += $(foreach curve, $(CURVE_APP_LOAD_PARAMS), --curve $(curve)) - -APP_LOAD_PARAMS += $(foreach path, $(PATH_APP_LOAD_PARAMS), --path $(path)) - -APP_LOAD_PARAMS += $(COMMON_LOAD_PARAMS) - # Pending review flag ifeq ($(ENABLE_PENDING_REVIEW_SCREEN), 1) - APP_LOAD_PARAMS += --tlvraw 9F:01 + TLVRAW_APP_LOAD_PARAMS += 9F:01 DEFINES += HAVE_PENDING_REVIEW_SCREEN endif +APP_FLAGS_APP_LOAD_PARAMS = $(shell printf '0x%x' $$(( $(STANDARD_APP_FLAGS) + $(CUSTOM_APP_FLAGS) )) ) + ##################################################################### # COMPILER SETTINGS # ##################################################################### diff --git a/extract_param.py b/extract_param.py new file mode 100644 index 000000000..0dc614a5f --- /dev/null +++ b/extract_param.py @@ -0,0 +1,20 @@ +""" +Helper to extract APP_LOAD_PARAMS parameters values. +It takes as a first parameter the parameter name to be search and output the +corresponding values from the rest of the script parameters. +""" + +from sys import argv + +if __name__ == '__main__': + + assert len(argv) > 2 + searching = argv[1] + + res = [] + args = argv[2:] + + for i, arg in enumerate(args): + if arg == searching and len(args) > i: + res.append(repr(args[i + 1])) + print(" ".join(res)) diff --git a/install_params.py b/install_params.py new file mode 100644 index 000000000..3110d9238 --- /dev/null +++ b/install_params.py @@ -0,0 +1,192 @@ +""" +/******************************************************************************* + * Ledger - Secure firmware + * (c) 2023 Ledger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ********************************************************************************/ +""" + +import struct +import argparse +import binascii + + +BOLOS_TAG_APPNAME = 1 +BOLOS_TAG_APPVERSION = 2 +BOLOS_TAG_ICON = 3 +BOLOS_TAG_DERIVEPATH = 4 +BOLOS_TAG_DEPENDENCY = 6 + + +CURVE_SECP256K1 = 1 << 0 +CURVE_PRIME256R1 = 1 << 1 +CURVE_ED25519 = 1 << 2 +CURVE_SLIP21 = 1 << 3 +CURVE_BLS12381G1 = 1 << 4 + + +def auto_int(x): + return int(x, 0) + + +def string_to_bytes(x): + return bytes(x, 'ascii') + + +def encodelv(value): + length = len(value) + res = b"" + if length < 128: + res += struct.pack(">B", length) + elif length < 256: + res += struct.pack(">B", 0x81) + res += struct.pack(">B", length) + elif length < 65536: + res += struct.pack(">B", 0x82) + res += struct.pack(">H", length) + else: + raise Exception("Unimplemented LV encoding") + res += value + return res + + +def encodetlv(tag, value): + res = struct.pack(">B", tag) + res += encodelv(value) + return res + + +def get_curve_mask(curves, slip21_paths): + if curves: + curve_mask = 0x00 + for curve in curves: + if curve == 'secp256k1': + curve_mask |= CURVE_SECP256K1 + elif curve == 'secp256r1': + curve_mask |= CURVE_PRIME256R1 + elif curve == 'ed25519': + curve_mask |= CURVE_ED25519 + elif curve == 'bls12381g1': + curve_mask |= CURVE_BLS12381G1 + else: + raise Exception("Unknown curve " + curve) + + if slip21_paths: + curve_mask |= CURVE_SLIP21 + else: + curve_mask = 0xff + + return curve_mask + + +def parse_bip32_path(path): + elements = path.split('/') + result = struct.pack('>B', len(elements)) + for element in elements: + if element.endswith("\'"): + value = 0x80000000 | int(element[:-1]) + else: + value = int(element) + result += struct.pack(">I", value) + return result + + +def parse_slip21_path(path): + result = struct.pack('>B', 0x80 | (len(path) + 1)) + result = result + b'\x00' + string_to_bytes(path) + return result + + +def get_serialized_path(curves, bip32_paths, slip21_paths): + curve_mask = get_curve_mask(args.curve, args.path_slip21) + serialized_path = struct.pack('>B', curve_mask) + + serialized_bip32_paths = b"" + if bip32_paths: + for path in bip32_paths: + if path: + serialized_bip32_paths += parse_bip32_path(path) + serialized_path += serialized_bip32_paths + + serialized_slip21_path = b"" + if slip21_paths: + for path in slip21_paths: + if path: + serialized_slip21_path += parse_slip21_path(path) + if not serialized_bip32_paths: + # Unrestricted, authorize all paths for regular derivation + serialized_slip21_path += struct.pack('>B', 0) + serialized_path += serialized_slip21_path + + return serialized_path + + +def get_argparser(): + parser = argparse.ArgumentParser(description="Generate application install_params TLV bytes.") + parser.add_argument("--appName", help="The name to give the application after loading it", required=True) + parser.add_argument("--appVersion", help="The application version (as a string)") + parser.add_argument("--icon", help="The icon content to use (hex encoded)") + parser.add_argument("--curve", help="""A curve on which BIP 32 derivation is locked ("secp256k1", "secp256r1", +"ed25519" or "bls12381g1"), can be repeated""", action='append') + parser.add_argument("--path", help="""A BIP 32 path to which derivation is locked (format decimal a'/b'/c), can be +repeated""", action='append') + parser.add_argument("--path_slip21", help="""A SLIP 21 path to which derivation is locked""", action='append') + parser.add_argument("--tlvraw", help="Add a custom install param with the hextag:hexvalue encoding", action='append') + parser.add_argument("--dep", help="Add a dependency over an appname[:appversion]", action='append') + + return parser + + +if __name__ == '__main__': + + args = get_argparser().parse_args() + + # Build install parameters + install_params = b"" + + # express dependency + if args.dep: + for dep in args.dep: + app_name, app_version = dep, None + + # split if version is specified + if ":" in dep: + app_name, app_version = dep.split(":") + + dep_value = encodelv(string_to_bytes(app_name)) + if app_version: + dep_value += encodelv(string_to_bytes(app_version)) + install_params += encodetlv(BOLOS_TAG_DEPENDENCY, dep_value) + + # Add raw install parameters as requested + if args.tlvraw: + for tlvraw in args.tlvraw: + hextag, hexvalue = tlvraw.split(":") + install_params += encodetlv(int(hextag, 16), binascii.unhexlify(hexvalue)) + + # App name is mandatory + install_params += encodetlv(BOLOS_TAG_APPNAME, string_to_bytes(args.appName)) + + if args.appVersion: + install_params += encodetlv(BOLOS_TAG_APPVERSION, string_to_bytes(args.appVersion)) + + if args.icon: + install_params += encodetlv(BOLOS_TAG_ICON, bytes.fromhex(args.icon)) + + serialized_path = get_serialized_path(args.curve, args.path, args.path_slip21) + if len(serialized_path) > 0: + install_params += encodetlv(BOLOS_TAG_DERIVEPATH, serialized_path) + + output = ",".join(f"0x{i:02x}" for i in install_params) + print(output) diff --git a/src/app_metadata.c b/src/app_metadata.c index b46a4dd99..ab2abf277 100644 --- a/src/app_metadata.c +++ b/src/app_metadata.c @@ -68,4 +68,9 @@ CREATE_METADATA_STRING_ITEM(SDK_VERSION, sdk_version) CREATE_METADATA_STRING_ITEM(SDK_HASH, sdk_hash) #endif +#ifdef APP_INSTALL_PARAMS_DATA +__attribute__((section(".install_parameters"))) const uint8_t install_parameters[] + = {APP_INSTALL_PARAMS_DATA}; +#endif + #endif diff --git a/src/pic.c b/src/pic.c index aee063083..8bfb56cd2 100644 --- a/src/pic.c +++ b/src/pic.c @@ -20,14 +20,14 @@ __attribute__((naked, no_instrument_function)) void *pic_internal(void *link_add // only apply PIC conversion if link_address is in linked code (over 0xC0D00000 in our example) // this way, PIC call are armless if the address is not meant to be converted extern void _nvram; -extern void _envram; +extern void _install_parameters; #if defined(ST31) void *pic(void *link_address) { // check if in the LINKED TEXT zone - if (link_address >= &_nvram && link_address < &_envram) { + if (link_address >= &_nvram && link_address < &_install_parameters) { link_address = pic_internal(link_address); } @@ -45,7 +45,7 @@ void *pic(void *link_address) // check if in the LINKED TEXT zone __asm volatile("ldr %0, =_nvram" : "=r"(n)); - __asm volatile("ldr %0, =_envram" : "=r"(en)); + __asm volatile("ldr %0, =_install_parameters" : "=r"(en)); if (link_address >= n && link_address <= en) { link_address = pic_internal(link_address); } diff --git a/target/nanos/script.ld b/target/nanos/script.ld index aeb948b34..447f13ff6 100644 --- a/target/nanos/script.ld +++ b/target/nanos/script.ld @@ -81,6 +81,9 @@ SECTIONS _envram_data = .; _install_parameters = .; + *(.install_parameters) + KEEP(*(.install_parameters)) + _einstall_parameters = .; _nvram_end = .; } > FLASH = 0x00 diff --git a/target/nanos2/script.ld b/target/nanos2/script.ld index e89c97749..62d995fa7 100644 --- a/target/nanos2/script.ld +++ b/target/nanos2/script.ld @@ -75,6 +75,9 @@ SECTIONS _envram_data = .; _install_parameters = .; + *(.install_parameters) + KEEP(*(.install_parameters)) + _einstall_parameters = .; _nvram_end = .; } > FLASH = 0x00 diff --git a/target/nanox/script.ld b/target/nanox/script.ld index fb850ad0e..aaca967eb 100644 --- a/target/nanox/script.ld +++ b/target/nanox/script.ld @@ -77,6 +77,9 @@ SECTIONS _envram_data = .; _install_parameters = .; + *(.install_parameters) + KEEP(*(.install_parameters)) + _einstall_parameters = .; _nvram_end = .; } > FLASH diff --git a/target/stax/script.ld b/target/stax/script.ld index f23457928..ae52569cf 100644 --- a/target/stax/script.ld +++ b/target/stax/script.ld @@ -76,6 +76,9 @@ SECTIONS _envram_data = .; _install_parameters = .; + *(.install_parameters) + KEEP(*(.install_parameters)) + _einstall_parameters = .; _nvram_end = .; } > FLASH = 0x00