Skip to content

Scripts: Security enablement during west build #87669

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions boards/silabs/radio_boards/siwx917_rb4338a/board.cmake
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Copyright (c) 2024 Silicon Laboratories Inc.
# SPDX-License-Identifier: Apache-2.0

# Set the flash file based on signing is enabled or not.
set(FLASH_FILE ${PROJECT_BINARY_DIR}/zephyr_signed.bin.rps)
if(NOT EXISTS ${FLASH_FILE})
set(FLASH_FILE ${PROJECT_BINARY_DIR}/zephyr.bin.rps)
endif()
board_runner_args(silabs_commander "--device=SiWG917M111GTBA" "--file-type=bin"
"--file=${PROJECT_BINARY_DIR}/${KERNEL_BIN_NAME}.rps")
"--file=${FLASH_FILE}")
include(${ZEPHYR_BASE}/boards/common/silabs_commander.board.cmake)

# It is not possible to load/flash a firmware using JLink, but it is possible to
Expand Down
117 changes: 115 additions & 2 deletions scripts/west_commands/sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import sys

from elftools.elf.elffile import ELFFile

from west import manifest
from west.commands import Verbosity
from west.util import quote_sh_list
Expand All @@ -23,6 +22,7 @@
from runners.core import BuildConfiguration
from zcmake import CMakeCache
from zephyr_ext_common import Forceable, ZEPHYR_SCRIPTS
from zephyr_ext_common import ZEPHYR_BASE

# This is needed to load edt.pickle files.
sys.path.insert(0, str(ZEPHYR_SCRIPTS / 'dts' / 'python-devicetree' / 'src'))
Expand Down Expand Up @@ -79,6 +79,21 @@
[rimage] sections in your west config file(s); this is especially useful
when invoking west sign _indirectly_ through CMake/ninja. See how at
https://docs.zephyrproject.org/latest/develop/west/sign.html

silabs_commander
-----

To create a signed binary with the silabs_commander tool, run this from your build directory:

west sign -t silabs_commander -- --mic YOUR_OTA_KEY --encrypt YOUR_OTA_KEY --sign YOUR_PRIVATE_KEY.pem
or west sign -t silabs_commander

For this to work, Silabs Commander tool must be installed in your PATH.

The input binary (zephyr.bin.rps) is signed using the specified OTA key and private key,
producing zephyr.bin.rps by default. If CONFIG_M4_OTA_KEY and CONFIG_M4_PRIVATE_KEY are set
in your build configuration, they will be used unless overridden by command-line arguments.
Additional arguments after '--' are passed to silabs_commander directly.
'''

class ToggleAction(argparse.Action):
Expand Down Expand Up @@ -112,7 +127,7 @@ def do_add_parser(self, parser_adder):

# general options
group = parser.add_argument_group('tool control options')
group.add_argument('-t', '--tool', choices=['imgtool', 'rimage'],
group.add_argument('-t', '--tool', choices=['imgtool', 'rimage', 'silabs_commander'],
help='''image signing tool name; imgtool and rimage
are currently supported (imgtool is deprecated)''')
group.add_argument('-p', '--tool-path', default=None,
Expand Down Expand Up @@ -195,6 +210,8 @@ def do_run(self, args, ignored):
signer = ImgtoolSigner()
elif args.tool == 'rimage':
signer = RimageSigner()
elif args.tool == 'silabs_commander':
signer = SilabsRPSSigner()
# (Add support for other signers here in elif blocks)
else:
if args.tool is None:
Expand Down Expand Up @@ -633,3 +650,99 @@ def sign(self, command, build_dir, build_conf, formats):

os.remove(out_bin)
os.rename(out_tmp, out_bin)

class SilabsRPSSigner(Signer):
# Signer class for Silicon Labs Commander tool via west sign -t silabs_commander.
def find_commandertool(self, cmd, args):
if args.tool_path:
commandertool = args.tool_path
if not os.path.isfile(commandertool):
cmd.die(f'--tool-path {commandertool}: no such file')
else:
commandertool = shutil.which('commander')
if not commandertool:
cmd.die('commander not found')
return commandertool

def get_security_configs(self, build_conf, args, command):
# Retrieve configurations, prioritizing command-line args, then build_conf
m4_ota_key = getattr(args, 'm4-ota-key', build_conf.get('CONFIG_M4_OTA_KEY'))
m4_private_key = getattr(args, 'm4-private-key', build_conf.get('CONFIG_M4_PRIVATE_KEY'))

# Use west sign's --tool-path to find the commander tool
commander_path = self.find_commandertool(command, args)

# Validate required settings
if not m4_ota_key:
command.die("M4_OTA_KEY not provided via --m4-ota-key or set in prj.conf. Please specify --m4-ota-key=your_key or add CONFIG_M4_OTA_KEY in prj.conf.")
if not m4_private_key:
command.die("M4_PRIVATE_KEY not provided via --m4-private-key or set in prj.conf. Please specify --m4-private-key=/path/to/key or add CONFIG_M4_PRIVATE_KEY in prj.conf.")

return m4_ota_key, m4_private_key, commander_path

def sign(self, command, build_dir, build_conf, formats):
"""Sign the Zephyr binary using Silicon Labs Commander.

:param command: The Sign instance (provides args and utility methods)
:param build_dir: The build directory path
:param build_conf: BuildConfiguration object for the build directory
:param formats: List of formats to generate (e.g., ['bin', 'rps'])
"""
self.command = command
args = command.args
b = pathlib.Path(build_dir)

# Check if signing is needed
if not formats:
if not args.quiet:
command.inf("No output formats specified, skipping signing.")
return

zephyr_base = ZEPHYR_BASE
if not zephyr_base:
command.die("ZEPHYR_BASE could not be retrieved. Ensure you are running within a Zephyr build environment.")
workspace_base = pathlib.Path(zephyr_base)
if not workspace_base.is_dir():
command.die(f"Invalid ZEPHYR_BASE directory: {ZEPHYR_BASE}. Please check your Zephyr setup.")

# Get kernel binary name (default to 'zephyr')
kernel_name = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Kconfig already provide default values and you shouldn't manage default values here.


# Setting the input and output file directories
input_rps = b / 'zephyr' / f"{kernel_name}.bin.rps"
output_rps = args.sbin or (b / 'zephyr' / f"{kernel_name}_signed.bin.rps")

# Check if input binary exists
if not input_rps.is_file():
command.die(f"No .rps found at {input_rps}. Ensure the build generated {kernel_name}.bin.rps in {b / 'zephyr'}")

# Load configuration
m4_ota_key, m4_private_key, commander_path = self.get_security_configs(build_conf, args, command)

# Resolve paths
tool_path = pathlib.Path(commander_path)
if not tool_path.is_file():
command.die(f"Silicon Labs Commander not found at {tool_path}. Please ensure it is installed or provide --tool-path.")

private_key_path = pathlib.Path(m4_private_key)
if not private_key_path.is_file():
command.die(f"Private key not found at {private_key_path}. Please ensure the path is correct.")

# Build the Silicon Labs Commander signing command
sign_base = [
str(tool_path),
"rps",
"convert",
str(output_rps),
"--app", str(input_rps),
"--mic", m4_ota_key,
"--encrypt", m4_ota_key,
"--sign", str(private_key_path)
]

sign_base.extend(args.tool_args)

try:
subprocess.check_call(sign_base, stdout=subprocess.PIPE if args.quiet else None, stderr=subprocess.PIPE if args.quiet else None)
except subprocess.CalledProcessError as e:
command.die(f"Silicon Labs Commander signing failed with exit code {e.returncode}")
31 changes: 31 additions & 0 deletions soc/silabs/silabs_siwx91x/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Copyright (c) 2024 Silicon Laboratories Inc.
# SPDX-License-Identifier: Apache-2.0

include(west) # Add this at the top or before using ${WEST}

add_subdirectory(siwg917)
zephyr_include_directories(.)

# Necessary to not overwrite NWP Firmware
math(EXPR FLASH_LOAD_ADDRESS "(${CONFIG_FLASH_BASE_ADDRESS}) + (${CONFIG_FLASH_LOAD_OFFSET})")
Expand All @@ -12,3 +15,31 @@ set_property(GLOBAL APPEND PROPERTY extra_post_build_commands
${KERNEL_BIN_NAME}
${KERNEL_BIN_NAME}.rps
)

# Custom signing with Silicon Labs Commander
if(CONFIG_SiWX91X_SILABS_COMMANDER_SIGN)
# Define the west sign command to run along with west build command
set(WEST_SIGN_CMD
${CMAKE_COMMAND} -E env "PATH=$ENV{PATH}:$ENV{WEST_ROOT}/bin" "ZEPHYR_WORKSPACE_ROOT=${CMAKE_CURRENT_SOURCE_DIR}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you change the PATH?

I am surprised you need ZEPHYR_WORKSPACE_ROOT. There is not other way to retrieve this value?

${WEST} sign
-t silabs_commander
-d ${CMAKE_BINARY_DIR}
--sbin ${PROJECT_BINARY_DIR}/${KERNEL_BIN_NAME}.rps
)

# Add custom command to invoke west sign during build
add_custom_command(
OUTPUT ${KERNEL_BIN_NAME}.rps
COMMAND ${CMAKE_COMMAND} -E echo "Forcing execution in ${PROJECT_BINARY_DIR}"
COMMAND ${WEST_SIGN_CMD}
DEPENDS zephyr_final
VERBATIM
)

# Add custom target to ensure signing is part of the build
add_custom_target(silabs_commander_sign
ALL
DEPENDS ${KERNEL_BIN_NAME}.rps
COMMENT "Ensuring signing with Silicon Labs Commander is complete"
)
endif()
16 changes: 16 additions & 0 deletions soc/silabs/silabs_siwx91x/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,19 @@ config SOC_SILABS_SLEEPTIMER
bool
help
The Sleeptimer HAL module is used for SIWX91X.

config SIWX91X_SILABS_COMMANDER_SIGN
bool "Silicon Labs Commander Signing Support"
depends on BUILD_OUTPUT_BIN
help
Activates the signing process for the Zephyr binary using Silicon Labs Commander during the build process.

config M4_OTA_KEY
string "OTA Key for Encryption (sourced from prj.conf)"
help
Specifies the OTA key (in hex format) for signing and encryption with Silicon Labs Commander, retrieved from prj.conf.

config M4_PRIVATE_KEY
string "Private Key Path for Signing (sourced from prj.conf)"
help
Defines the path to the private key file for signing with Silicon Labs Commander, obtained from prj.conf, relative to the board directory.
Loading