Skip to content

Commit 7052317

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 2338f8e commit 7052317

File tree

3 files changed

+179
-6
lines changed

3 files changed

+179
-6
lines changed

CMakeLists.txt

+89-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
#
99
# To see a list of typical targets execute "make usage"
1010
# More info can be located in ./README.rst
11-
# Comments in this file are targeted only to the developer, do not
12-
# expect to learn how to build the kernel reading this file.
13-
1411
if(NOT DEFINED ZEPHYR_BINARY_DIR)
1512
message(FATAL_ERROR "A user error has occurred.
1613
cmake was invoked with '${CMAKE_CURRENT_LIST_DIR}' specified as the source directory,
@@ -2303,3 +2300,92 @@ build_info(zephyr version VALUE ${PROJECT_VERSION_STR})
23032300
build_info(zephyr zephyr-base VALUE ${ZEPHYR_BASE})
23042301

23052302
yaml_save(NAME build_info)
2303+
# Locate the .west/config file (assuming it's in the workspace root, i.e., $PWD)
2304+
set(WEST_CONFIG_FILE "$ENV{PWD}/.west/config")
2305+
2306+
# Read the config file content and extract signing parameters
2307+
if(EXISTS ${WEST_CONFIG_FILE})
2308+
file(READ ${WEST_CONFIG_FILE} WEST_CONFIG_CONTENT)
2309+
# Extract sign_firmware value from [sign] section
2310+
string(REGEX MATCH "sign_firmware = ([0-1])" SIGN_FIRMWARE_MATCH "${WEST_CONFIG_CONTENT}")
2311+
if(CMAKE_MATCH_1)
2312+
set(SIGN_FIRMWARE ${CMAKE_MATCH_1})
2313+
else()
2314+
set(SIGN_FIRMWARE 0) # Default to 0 if not found
2315+
endif()
2316+
2317+
# Extract tool path from [sign.customtool] section, ensure absolute path
2318+
string(REGEX MATCH "tool-path = ([^\n]+)" TOOL_PATH_MATCH "${WEST_CONFIG_CONTENT}")
2319+
if(CMAKE_MATCH_1)
2320+
set(CUSTOM_SIGN_TOOL_PATH "${CMAKE_MATCH_1}")
2321+
else()
2322+
# Use absolute path based on $ENV{PWD}
2323+
set(CUSTOM_SIGN_TOOL_PATH "$ENV{PWD}/zephyr/scripts/west_commands/sign_rps.py")
2324+
endif()
2325+
2326+
# Extract m4_ota_key from [sign.customtool] section
2327+
string(REGEX MATCH "m4-ota-key = ([^\n]+)" M4_OTA_KEY_MATCH "${WEST_CONFIG_CONTENT}")
2328+
set(M4_OTA_KEY "${CMAKE_MATCH_1}")
2329+
2330+
# Extract m4_private_key from [sign.customtool] section
2331+
string(REGEX MATCH "m4-private-key = ([^\n]+)" M4_PRIVATE_KEY_MATCH "${WEST_CONFIG_CONTENT}")
2332+
set(M4_PRIVATE_KEY "${CMAKE_MATCH_1}")
2333+
else()
2334+
message(WARNING "West config file not found at ${WEST_CONFIG_FILE}. Using default signing settings.")
2335+
set(SIGN_FIRMWARE 0) # Default to 0 if config file is missing
2336+
endif()
2337+
2338+
# Ensure CUSTOM_SIGN_TOOL_PATH is absolute
2339+
if(NOT IS_ABSOLUTE "${CUSTOM_SIGN_TOOL_PATH}")
2340+
set(CUSTOM_SIGN_TOOL_PATH "$ENV{PWD}/zephyr/scripts/west_commands/sign_rps.py")
2341+
endif()
2342+
2343+
message(STATUS "SIGN_FIRMWARE = ${SIGN_FIRMWARE}")
2344+
message(STATUS "CUSTOM_SIGN_TOOL_PATH = ${CUSTOM_SIGN_TOOL_PATH}")
2345+
message(STATUS "M4_OTA_KEY = ${M4_OTA_KEY}")
2346+
message(STATUS "M4_PRIVATE_KEY = ${M4_PRIVATE_KEY}")
2347+
2348+
# Define paths for input, intermediate, and output files
2349+
set(ZEPHYR_BIN "${CMAKE_BINARY_DIR}/zephyr/zephyr.bin.rps") # Input file
2350+
set(ZEPHYR_SIGNED_RPS "${CMAKE_BINARY_DIR}/zephyr/zephyr.signed.rps") # Blank signed file
2351+
set(ZEPHYR_BIN_RPS "${CMAKE_BINARY_DIR}/zephyr/zephyr.bin.rps") # Final output file
2352+
2353+
# Conditionally define the custom signing command
2354+
if(SIGN_FIRMWARE EQUAL 1)
2355+
# Define the custom signing command with conditions
2356+
add_custom_command(
2357+
OUTPUT ${ZEPHYR_BIN_RPS} ${ZEPHYR_SIGNED_RPS}
2358+
COMMAND ${CMAKE_COMMAND} -E echo "Checking custom signing conditions after build..."
2359+
COMMAND sh -c "if [ -f '${ZEPHYR_BIN}' ] && [ ! -f '${ZEPHYR_SIGNED_RPS}' ]; then \
2360+
echo 'Conditions met: sign_firmware=1, zephyr.bin exists, zephyr.signed.bin does not exist'; \
2361+
touch '${ZEPHYR_SIGNED_RPS}' && \
2362+
${PYTHON_EXECUTABLE} '${CUSTOM_SIGN_TOOL_PATH}' \
2363+
--input '${ZEPHYR_BIN}' \
2364+
--output '${ZEPHYR_BIN_RPS}' \
2365+
--m4_ota_key '${M4_OTA_KEY}' \
2366+
--m4_private_key '${M4_PRIVATE_KEY}'; \
2367+
else \
2368+
echo 'Skipping signing: conditions not met'; \
2369+
echo 'zephyr.bin exists? ' && ls -l '${ZEPHYR_BIN}' || echo 'No'; \
2370+
echo 'zephyr.signed.bin exists? ' && ls -l '${ZEPHYR_SIGNED_RPS}' || echo 'No'; \
2371+
fi"
2372+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
2373+
DEPENDS ${logical_target_for_zephyr_elf} # Ensure this runs after the final ELF is built
2374+
COMMENT "Custom signing of ${ZEPHYR_BIN} to ${ZEPHYR_BIN_RPS}, creating blank ${ZEPHYR_SIGNED_RPS}"
2375+
VERBATIM
2376+
)
2377+
2378+
# Add a custom target to trigger the signing
2379+
add_custom_target(custom_signing
2380+
DEPENDS ${ZEPHYR_BIN_RPS} ${ZEPHYR_SIGNED_RPS}
2381+
)
2382+
2383+
# Ensure signing runs post-build by adding it to the final target dependencies
2384+
add_custom_target(final_custom_signing ALL
2385+
DEPENDS ${logical_target_for_zephyr_elf} custom_signing
2386+
COMMENT "Ensuring custom signing runs post-build"
2387+
)
2388+
else()
2389+
message(STATUS "Custom firmware signing skipped (sign_firmware = 0)")
2390+
endif()
2391+

scripts/west_commands/sign.py

+51-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
import sys
1414

1515
from elftools.elf.elffile import ELFFile
16-
16+
# custom tool signer
17+
from west.configuration import config
1718
from west import manifest
1819
from west.commands import Verbosity
1920
from west.util import quote_sh_list
@@ -112,10 +113,10 @@ def do_add_parser(self, parser_adder):
112113

113114
# general options
114115
group = parser.add_argument_group('tool control options')
115-
group.add_argument('-t', '--tool', choices=['imgtool', 'rimage'],
116+
group.add_argument('-t', '--tool', choices=['imgtool', 'rimage', 'customtool'],
116117
help='''image signing tool name; imgtool and rimage
117118
are currently supported (imgtool is deprecated)''')
118-
group.add_argument('-p', '--tool-path', default=None,
119+
group.add_argument('-p', '--tool-path', default='/zephyr/scripts/west_commands/sign_rps.py',
119120
help='''path to the tool itself, if needed''')
120121
group.add_argument('-D', '--tool-data', default=None,
121122
help='''path to a tool-specific data/configuration directory, if needed''')
@@ -195,6 +196,8 @@ def do_run(self, args, ignored):
195196
signer = ImgtoolSigner()
196197
elif args.tool == 'rimage':
197198
signer = RimageSigner()
199+
elif args.tool == 'customtool':
200+
signer = CustomToolSigner()
198201
# (Add support for other signers here in elif blocks)
199202
else:
200203
if args.tool is None:
@@ -633,3 +636,48 @@ def sign(self, command, build_dir, build_conf, formats):
633636

634637
os.remove(out_bin)
635638
os.rename(out_tmp, out_bin)
639+
class CustomToolSigner(Signer):
640+
def sign(self, command, build_dir, build_conf, formats):
641+
if not formats:
642+
return
643+
args = command.args
644+
b = pathlib.Path(build_dir)
645+
kernel_name = 'zephyr' # Default if build_conf is None
646+
if build_conf:
647+
kernel_name = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr')
648+
in_bin = b / 'zephyr' / f'{kernel_name}.bin.rps'
649+
out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.bin.rps')
650+
if not in_bin.is_file():
651+
command.die(f"no unsigned .bin found at {in_bin}")
652+
# Retrieve values from .west/config [sign.customtool]
653+
m4_ota_key = config.get('sign.customtool', 'm4-ota-key', fallback=None)
654+
m4_private_key = config.get('sign.customtool', 'm4-private-key', fallback=None)
655+
tool_path_rel = config.get('sign.customtool', 'tool-path', fallback=None)
656+
# Validate required values
657+
if not m4_ota_key:
658+
command.die("Missing 'm4-ota-key' in .west/config [sign.customtool] section")
659+
if not m4_private_key:
660+
command.die("Missing 'm4-private-key' in .west/config [sign.customtool] section")
661+
if not tool_path_rel:
662+
command.die("Missing 'tool-path' in .west/config [sign.customtool] section")
663+
# Resolve tool-path relative to workspace root (directory containing .west/)
664+
tool_path = os.path.abspath(os.path.join(os.environ['PWD'], tool_path_rel))
665+
# Debug output to verify path
666+
if not args.quiet:
667+
command.inf(f"Resolved tool_path: {tool_path}")
668+
# Ensure tool_path is executable
669+
if not os.path.isfile(tool_path) or not os.access(tool_path, os.X_OK):
670+
command.die(f"Tool path {tool_path} is not a valid executable file")
671+
load_rps_command = [
672+
sys.executable, tool_path,
673+
'--input', str(in_bin),
674+
'--output', out_bin,
675+
'--m4_ota_key', m4_ota_key,
676+
'--m4_private_key', m4_private_key,
677+
] + args.tool_args
678+
if not args.quiet:
679+
command.inf(f"Running signing command: {' '.join(load_rps_command)}")
680+
try:
681+
subprocess.check_call(load_rps_command, stdout=subprocess.PIPE if args.quiet else None)
682+
except subprocess.CalledProcessError as e:
683+
command.die(f"Signing failed with exit code {e.returncode}")

scripts/west_commands/sign_rps.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#SPDX - License - Identifier : Apache - 2.0#!/ usr / bin / env python3
2+
#Copyright(c) 2018 Foundries.io
3+
#
4+
import argparse
5+
import os
6+
import subprocess
7+
from west.configuration import config
8+
parser = argparse.ArgumentParser(description = "Sign a Zephyr binary using SimplicityCommander")
9+
parser.add_argument('--m4_ota_key', help = "M4 OTA Key (hex string)")
10+
parser.add_argument('--m4_private_key', help = "M4 Private Key (file path)")
11+
parser.add_argument('--input', required = True, help = "Input file path")
12+
parser.add_argument('--output', required = True, help = "Output file path")
13+
args = parser.parse_args()
14+
#Get the workspace root by navigating up from $PWD to the 'workspace' directory
15+
workspace_root = os.path.abspath(os.path.join(os.environ['PWD'], '..')) #Go up one level from 'build' to 'workspace'
16+
SL_COMMANDER_PATH = os.path.join(workspace_root, 'SimplicityCommander-Linux/commander/commander')
17+
#Check if SimplicityCommander exists and is executable
18+
if not os.path.isfile(SL_COMMANDER_PATH) or not os.access(SL_COMMANDER_PATH, os.X_OK) :
19+
raise FileNotFoundError(f "SimplicityCommander not found or not executable at '{SL_COMMANDER_PATH}'. "
20+
"Please ensure it exists and is executable at '<workspace_root>/SimplicityCommander-Linux/commander/commander'.")
21+
#Keep m4_ota_key and m4_private_key configurable via.west / config as in original code
22+
m4_ota_key = args.m4_ota_key or config.get('sign.customtool', 'm4-ota-key', fallback = None)
23+
m4_private_key = args.m4_private_key or config.get('sign.customtool', 'm4-private-key', fallback = None)
24+
if not m4_ota_key or not m4_private_key:
25+
raise ValueError("Missing required values: m4-ota-key or m4-private-key must be provided via args or .west/config [sign.customtool]")
26+
if not os.path.isabs(m4_private_key) :
27+
m4_private_key = os.path.abspath(os.path.join(workspace_root, 'zephyr', m4_private_key))
28+
def commander_function(command_list) :
29+
print(f"Running commander command: {' '.join(command_list)}")
30+
process = subprocess.run(command_list, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
31+
stdout = process.stdout.decode('utf-8')
32+
stderr = process.stderr.decode('utf-8')
33+
print(f"STDOUT: {stdout}")
34+
print(f"STDERR: {stderr}")
35+
process.check_returncode() #Raises CalledProcessError if non - zero exit
36+
SECURE_M4_RPS = 1
37+
if SECURE_M4_RPS:create_m4_secure_rps =[SL_COMMANDER_PATH, 'rps', 'convert', args.output, '--app', args.input, '--mic', m4_ota_key, '--encrypt', m4_ota_key, '--sign', m4_private_key]
38+
if SECURE_M4_RPS:commander_function(create_m4_secure_rps)
39+
print(f"Signed {args.input} -> {args.output}")

0 commit comments

Comments
 (0)