Skip to content

Commit c6cbbf6

Browse files
committed
Scripts: Security enablement during west build
Added changes to automate the signing of zephyr security using customtool during the west build process. Signed-off-by: Aasim Shaik <[email protected]>
1 parent e7d5905 commit c6cbbf6

File tree

3 files changed

+219
-2
lines changed

3 files changed

+219
-2
lines changed

scripts/west_commands/sign.py

+161-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import sys
1414

1515
from elftools.elf.elffile import ELFFile
16-
1716
from west import manifest
1817
from west.commands import Verbosity
1918
from west.util import quote_sh_list
@@ -79,6 +78,20 @@
7978
[rimage] sections in your west config file(s); this is especially useful
8079
when invoking west sign _indirectly_ through CMake/ninja. See how at
8180
https://docs.zephyrproject.org/latest/develop/west/sign.html
81+
82+
silabs_rps
83+
-----
84+
85+
To create a signed binary with the silabs_rps tool, run this from your build directory:
86+
87+
west sign -t silabs_rps -- --mic YOUR_OTA_KEY --encrypt YOUR_OTA_KEY --sign YOUR_PRIVATE_KEY.pem
88+
89+
For this to work, either silabs_rps commander must be installed in your PATH.
90+
91+
The input binary (zephyr.bin.rps) is signed using the specified OTA key and private key,
92+
producing zephyr.bin.rps by default. If CONFIG_M4_OTA_KEY and CONFIG_M4_PRIVATE_KEY are set
93+
in your build configuration, they will be used unless overridden by command-line arguments.
94+
Additional arguments after '--' are passed to silabs_rps directly.
8295
'''
8396

8497
class ToggleAction(argparse.Action):
@@ -112,7 +125,7 @@ def do_add_parser(self, parser_adder):
112125

113126
# general options
114127
group = parser.add_argument_group('tool control options')
115-
group.add_argument('-t', '--tool', choices=['imgtool', 'rimage'],
128+
group.add_argument('-t', '--tool', choices=['imgtool', 'rimage', 'silabs_rps'],
116129
help='''image signing tool name; imgtool and rimage
117130
are currently supported (imgtool is deprecated)''')
118131
group.add_argument('-p', '--tool-path', default=None,
@@ -195,6 +208,8 @@ def do_run(self, args, ignored):
195208
signer = ImgtoolSigner()
196209
elif args.tool == 'rimage':
197210
signer = RimageSigner()
211+
elif args.tool == 'silabs_rps':
212+
signer = SilabsRPSSigner()
198213
# (Add support for other signers here in elif blocks)
199214
else:
200215
if args.tool is None:
@@ -633,3 +648,147 @@ def sign(self, command, build_dir, build_conf, formats):
633648

634649
os.remove(out_bin)
635650
os.rename(out_tmp, out_bin)
651+
652+
class SilabsRPSSigner(Signer):
653+
"""Signer class for Silicon Labs Commander tool via west sign -t silabs_rps."""
654+
def get_security_configs(self, soc_kconfig, args, command):
655+
m4_ota_key = None
656+
m4_private_key = None
657+
commander_path = None
658+
659+
if soc_kconfig.is_file():
660+
if not args.quiet:
661+
command.inf(f"Parsing Kconfig.defconfig at {soc_kconfig}")
662+
with open(soc_kconfig) as f:
663+
current_config = None
664+
for line in f:
665+
line = line.strip()
666+
if line.startswith('config '):
667+
current_config = line.split('config ')[1].split()[0] # Extract config name
668+
elif line.startswith('default ') and current_config:
669+
value = line.split('default ')[1].strip().strip('"')
670+
if current_config == 'M4_OTA_KEY':
671+
m4_ota_key = value
672+
elif current_config == 'M4_PRIVATE_KEY':
673+
m4_private_key = value
674+
elif current_config == 'COMMANDER_PATH':
675+
commander_path = value
676+
else:
677+
if not args.quiet:
678+
command.inf(f"No Kconfig.defconfig found at {soc_kconfig}. Using default values.")
679+
680+
# Apply default values only if not set from Kconfig.defconfig
681+
m4_ota_key = m4_ota_key or "OTA_KEY_IN_HEX" # Default OTA key
682+
m4_private_key = m4_private_key or "M4_PRIVATE_KEY_PATH" # Updated default private key path
683+
commander_path = commander_path or "COMMANDER_PATH_HERE" # Updated default Commander path
684+
return m4_ota_key, m4_private_key, commander_path
685+
686+
def sign(self, command, build_dir, build_conf, formats):
687+
"""Sign the Zephyr binary using Silicon Labs Commander.
688+
689+
:param command: The Sign instance (provides args and utility methods)
690+
:param build_dir: The build directory path
691+
:param build_conf: BuildConfiguration object for the build directory
692+
:param formats: List of formats to generate (e.g., ['bin', 'rps'])
693+
"""
694+
self.command = command
695+
args = command.args
696+
b = pathlib.Path(build_dir) # BUILD_DIR from -d option
697+
698+
# Check if signing is needed
699+
if not formats:
700+
if not args.quiet:
701+
command.inf("No output formats specified, skipping signing.")
702+
return
703+
704+
# Determine workspace base based on context (automation vs. manual)
705+
zephyr_workspace_root = os.environ.get('ZEPHYR_WORKSPACE_ROOT')
706+
if zephyr_workspace_root:
707+
# Automation context: Use ZEPHYR_WORKSPACE_ROOT as the workspace root
708+
workspace_base = pathlib.Path(zephyr_workspace_root)
709+
if not workspace_base.is_dir():
710+
command.die(f"Invalid ZEPHYR_WORKSPACE_ROOT directory: {workspace_base}. Please check the environment variable.")
711+
soc_dir = 'GIVE PATH FOR KCONFIG.defconfig AFTER WORKSPACE' # Relative path from workspace root
712+
else:
713+
# Manual context: Assume current directory is workspace root
714+
workspace_base = pathlib.Path(os.getcwd())
715+
if not args.quiet:
716+
command.inf(f"ZEPHYR_WORKSPACE_ROOT not set, using current directory: {workspace_base}")
717+
soc_dir = 'GIVE PATH FOR KCONFIG.defconfig AFTER WORKSPACE' # Same relative path
718+
719+
soc_kconfig_base = workspace_base / soc_dir
720+
721+
# Get kernel binary name (default to 'zephyr')
722+
kernel_name = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr')
723+
724+
# Input and output files (using BUILD_DIR)
725+
in_bin = b / 'zephyr' / f"{kernel_name}.bin.rps" # Raw binary input (corrected)
726+
out_rps = args.sbin or (b / 'zephyr' / f"{kernel_name}.bin.rps") # Signed output
727+
728+
# Check if input binary exists
729+
if not in_bin.is_file():
730+
command.die(f"No unsigned .bin found at {in_bin}")
731+
732+
# Load configuration from Kconfig.defconfig
733+
soc_kconfig = soc_kconfig_base / 'Kconfig.defconfig'
734+
m4_ota_key, m4_private_key, commander_path = self.get_security_configs(soc_kconfig, args, command)
735+
736+
# Override with command-line arguments if provided
737+
m4_ota_key = getattr(args, 'm4-ota-key', m4_ota_key)
738+
m4_private_key = getattr(args, 'm4-private-key', m4_private_key)
739+
commander_path = getattr(args, 'commander-path', commander_path)
740+
741+
# Validate required settings
742+
if not m4_ota_key:
743+
command.die("M4_OTA_KEY not set in Kconfig.defconfig and no --m4-ota-key provided.")
744+
if not m4_private_key:
745+
command.die("M4_PRIVATE_KEY not set in Kconfig.defconfig and no --m4-private-key provided.")
746+
if not commander_path:
747+
command.die("COMMANDER_PATH not set in Kconfig.defconfig and no --commander-path provided.")
748+
749+
# Resolve paths
750+
tool_path = pathlib.Path(commander_path)
751+
if not tool_path.is_file():
752+
command.die(f"Commander not found at {tool_path}. Please ensure the path is correct.")
753+
754+
private_key_path = pathlib.Path(m4_private_key)
755+
if not private_key_path.is_file():
756+
command.die(f"Private key not found at {private_key_path}. Please ensure the path is correct.")
757+
758+
# Build the commander signing command
759+
sign_base = [
760+
str(tool_path),
761+
"rps",
762+
"convert",
763+
str(out_rps),
764+
"--app", str(in_bin),
765+
"--mic", m4_ota_key,
766+
"--encrypt", m4_ota_key,
767+
"--sign", str(private_key_path)
768+
]
769+
770+
sign_base.extend(args.tool_args)
771+
772+
if not args.quiet:
773+
command.inf(f"Signing with tool {tool_path}")
774+
command.inf(f"Unsigned binary: {in_bin}")
775+
command.inf(f"Signed output: {out_rps}")
776+
777+
try:
778+
subprocess.check_call(sign_base, stdout=subprocess.PIPE if args.quiet else None, stderr=subprocess.PIPE if args.quiet else None)
779+
except subprocess.CalledProcessError as e:
780+
command.die(f"Commander signing failed with exit code {e.returncode}")
781+
782+
if not args.quiet:
783+
command.inf(f"Successfully generated signed file: {out_rps}")
784+
785+
# Register the signer and add command-line arguments
786+
west_signers = {'silabs_rps': SilabsRPSSigner}
787+
788+
def add_parser(subparsers):
789+
"""Add custom arguments for the silabs_rps signer."""
790+
parser = subparsers.add_parser('silabs_rps', help='Sign with Silicon Labs Commander')
791+
parser.add_argument('--m4-ota-key', help='OTA key for signing (overrides Kconfig.defconfig default)')
792+
parser.add_argument('--m4-private-key', help='Absolute path to private key file (overrides Kconfig.defconfig default)')
793+
parser.add_argument('--commander-path', help='Absolute path to Commander executable (overrides Kconfig.defconfig default)')
794+
return parser

soc/silabs/silabs_siwx91x/CMakeLists.txt

+43
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
# Copyright (c) 2024 Silicon Laboratories Inc.
22
# SPDX-License-Identifier: Apache-2.0
33

4+
include(west) # Add this at the top or before using ${WEST}
5+
46
add_subdirectory(siwg917)
7+
zephyr_include_directories(.)
8+
zephyr_sources_ifdef(CONFIG_SOC_SERIES_SIWG917 soc.c)
9+
zephyr_sources_ifdef(CONFIG_WISECONNECT_NETWORK_STACK nwp_init.c)
510

611
# Necessary to not overwrite NWP Firmware
712
math(EXPR FLASH_LOAD_ADDRESS "(${CONFIG_FLASH_BASE_ADDRESS}) + (${CONFIG_FLASH_LOAD_OFFSET})")
@@ -12,3 +17,41 @@ set_property(GLOBAL APPEND PROPERTY extra_post_build_commands
1217
${KERNEL_BIN_NAME}
1318
${KERNEL_BIN_NAME}.rps
1419
)
20+
21+
# Custom signing with Silicon Labs Commander
22+
if(CONFIG_SILABS_COMMANDER_SIGN)
23+
24+
# Define input and output files
25+
set(ZEPHYR_BINARY ${CMAKE_BINARY_DIR}/zephyr/${KERNEL_BIN_NAME}.bin.rps)
26+
set(SIGNED_RPS ${CMAKE_BINARY_DIR}/zephyr/${KERNEL_BIN_NAME}.bin.rps)
27+
28+
# Define the west sign command with explicit working directory and environment
29+
set(WEST_SIGN_CMD
30+
${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} # Explicitly set working directory
31+
${CMAKE_COMMAND} -E env "PATH=$ENV{PATH}:$ENV{WEST_ROOT}/bin" "ZEPHYR_WORKSPACE_ROOT=${CMAKE_CURRENT_SOURCE_DIR}"
32+
${WEST} sign
33+
-t silabs_rps
34+
-d ${CMAKE_BINARY_DIR}
35+
--sbin ${SIGNED_RPS}
36+
)
37+
38+
# Add custom command to invoke west sign during build
39+
add_custom_command(
40+
OUTPUT ${SIGNED_RPS}
41+
COMMAND ${CMAKE_COMMAND} -E echo "Forcing execution in ${CMAKE_BINARY_DIR}"
42+
COMMAND ${WEST_SIGN_CMD}
43+
DEPENDS zephyr_final
44+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR} # Ensure working directory is set
45+
COMMENT "Signing binary with 'west sign -t silabs_rps' to generate ${KERNEL_BIN_NAME}.bin.rps"
46+
VERBATIM
47+
)
48+
49+
# Add custom target to ensure signing is part of the build
50+
add_custom_target(silabs_rps_sign_target
51+
ALL
52+
DEPENDS ${SIGNED_RPS}
53+
COMMENT "Ensuring signing with Silicon Labs Commander is complete"
54+
)
55+
else()
56+
message(STATUS "CONFIG_SILABS_COMMANDER_SIGN is not enabled")
57+
endif()

soc/silabs/silabs_siwx91x/Kconfig.defconfig

+15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,21 @@ configdefault SYS_CLOCK_TICKS_PER_SEC
1313
default 128 if !TICKLESS_KERNEL && SILABS_SLEEPTIMER_TIMER
1414
default 1024 if SILABS_SLEEPTIMER_TIMER
1515

16+
config SILABS_COMMANDER_SIGN
17+
default n
18+
select BUILD_OUTPUT_BIN
19+
20+
if SILABS_COMMANDER_SIGN
21+
config M4_OTA_KEY
22+
default "Full_OTA_KEY_in_HEX"
23+
24+
config M4_PRIVATE_KEY
25+
default "Full_path_to_m4_private_key.pem"
26+
27+
config COMMANDER_PATH
28+
default "Full_path_to_SimplicityCommander-Linux/commander/commander"
29+
endif
30+
1631
config WISECONNECT_NETWORK_STACK
1732
bool
1833
select CMSIS_RTOS_V2

0 commit comments

Comments
 (0)