|
13 | 13 | import sys
|
14 | 14 |
|
15 | 15 | from elftools.elf.elffile import ELFFile
|
16 |
| - |
17 | 16 | from west import manifest
|
18 | 17 | from west.commands import Verbosity
|
19 | 18 | from west.util import quote_sh_list
|
|
79 | 78 | [rimage] sections in your west config file(s); this is especially useful
|
80 | 79 | when invoking west sign _indirectly_ through CMake/ninja. See how at
|
81 | 80 | https://docs.zephyrproject.org/latest/develop/west/sign.html
|
| 81 | +
|
| 82 | +silabs_commander |
| 83 | +----- |
| 84 | +
|
| 85 | +To create a signed binary with the silabs_commander tool, run this from your build directory: |
| 86 | +
|
| 87 | + west sign -t silabs_commander -- --mic YOUR_OTA_KEY --encrypt YOUR_OTA_KEY --sign YOUR_PRIVATE_KEY.pem |
| 88 | +
|
| 89 | +For this to work, either silabs_commander 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_commander directly. |
82 | 95 | '''
|
83 | 96 |
|
84 | 97 | class ToggleAction(argparse.Action):
|
@@ -112,7 +125,7 @@ def do_add_parser(self, parser_adder):
|
112 | 125 |
|
113 | 126 | # general options
|
114 | 127 | 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_commander'], |
116 | 129 | help='''image signing tool name; imgtool and rimage
|
117 | 130 | are currently supported (imgtool is deprecated)''')
|
118 | 131 | group.add_argument('-p', '--tool-path', default=None,
|
@@ -195,6 +208,8 @@ def do_run(self, args, ignored):
|
195 | 208 | signer = ImgtoolSigner()
|
196 | 209 | elif args.tool == 'rimage':
|
197 | 210 | signer = RimageSigner()
|
| 211 | + elif args.tool == 'silabs_commander': |
| 212 | + signer = SilabsRPSSigner() |
198 | 213 | # (Add support for other signers here in elif blocks)
|
199 | 214 | else:
|
200 | 215 | if args.tool is None:
|
@@ -633,3 +648,111 @@ def sign(self, command, build_dir, build_conf, formats):
|
633 | 648 |
|
634 | 649 | os.remove(out_bin)
|
635 | 650 | os.rename(out_tmp, out_bin)
|
| 651 | + |
| 652 | +class SilabsRPSSigner(Signer): |
| 653 | + # Signer class for Silicon Labs Commander tool via west sign -t silabs_commander. |
| 654 | + def get_security_configs(self, build_conf, args, command): |
| 655 | + # Retrieve configurations, prioritizing command-line args, then build_conf |
| 656 | + m4_ota_key = getattr(args, 'm4-ota-key', build_conf.get('CONFIG_M4_OTA_KEY')) |
| 657 | + m4_private_key = getattr(args, 'm4-private-key', build_conf.get('CONFIG_M4_PRIVATE_KEY')) |
| 658 | + # Fallback to directly read prj.conf if build_conf values are missing |
| 659 | + if not m4_ota_key or not m4_private_key: |
| 660 | + prj_conf_path = pathlib.Path(build_conf.build_dir) / 'prj.conf' |
| 661 | + if prj_conf_path.exists(): |
| 662 | + with open(prj_conf_path, 'r') as f: |
| 663 | + for line in f: |
| 664 | + if line.strip().startswith('CONFIG_M4_OTA_KEY='): |
| 665 | + m4_ota_key = line.split('=')[1].strip().strip('"') |
| 666 | + elif line.strip().startswith('CONFIG_M4_PRIVATE_KEY='): |
| 667 | + m4_private_key = line.split('=')[1].strip().strip('"') |
| 668 | + # Automatically detect Commander path, mimicking west flash |
| 669 | + commander_path = getattr(args, 'commander-path', None) |
| 670 | + if not commander_path: |
| 671 | + # Try system PATH first, as west flash likely does |
| 672 | + commander_path = shutil.which('commander') |
| 673 | + if not commander_path: |
| 674 | + # Fallback to common Silicon Labs default paths (adjust if known) |
| 675 | + default_paths = [ |
| 676 | + '/usr/bin/commander', |
| 677 | + '/usr/local/bin/commander', |
| 678 | + '/opt/SiliconLabs/Commander/commander' |
| 679 | + ] |
| 680 | + for path in default_paths: |
| 681 | + if os.path.isfile(path): |
| 682 | + commander_path = path |
| 683 | + break |
| 684 | + # Validate required settings with fallback to prj.conf |
| 685 | + if not m4_ota_key: |
| 686 | + 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.") |
| 687 | + if not m4_private_key: |
| 688 | + 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.") |
| 689 | + if not commander_path: |
| 690 | + command.die("Commander not found automatically. Please provide --commander-path=/path/to/commander or install it in your PATH.") |
| 691 | + |
| 692 | + return m4_ota_key, m4_private_key, commander_path |
| 693 | + |
| 694 | + def sign(self, command, build_dir, build_conf, formats): |
| 695 | + # Sign the Zephyr binary using Silicon Labs Commander. |
| 696 | + |
| 697 | + self.command = command |
| 698 | + args = command.args |
| 699 | + b = pathlib.Path(build_dir) # BUILD_DIR from -d option |
| 700 | + |
| 701 | + # Check if signing is needed |
| 702 | + if not formats: |
| 703 | + if not args.quiet: |
| 704 | + command.inf("No output formats specified, skipping signing.") |
| 705 | + return |
| 706 | + |
| 707 | + # Use ZEPHYR_BASE as the workspace base, provided by Zephyr's build system |
| 708 | + zephyr_base = os.environ.get('ZEPHYR_BASE') |
| 709 | + if not zephyr_base: |
| 710 | + command.die("ZEPHYR_BASE environment variable not set. Ensure you are running within a Zephyr build environment.") |
| 711 | + workspace_base = pathlib.Path(zephyr_base) |
| 712 | + if not workspace_base.is_dir(): |
| 713 | + command.die(f"Invalid ZEPHYR_BASE directory: {workspace_base}. Please check your Zephyr setup.") |
| 714 | + |
| 715 | + # Get kernel binary name (default to 'zephyr') |
| 716 | + kernel_name = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr') |
| 717 | + |
| 718 | + # Input and output files (using BUILD_DIR) |
| 719 | + in_bin = b / 'zephyr' / f"{kernel_name}.bin.rps" # Raw binary input |
| 720 | + out_rps = args.sbin or (b / 'zephyr' / f"{kernel_name}.bin.rps") # Signed output |
| 721 | + |
| 722 | + # Check if input binary exists |
| 723 | + if not in_bin.is_file(): |
| 724 | + command.die(f"No unsigned .bin found at {in_bin}") |
| 725 | + |
| 726 | + # Load configuration |
| 727 | + m4_ota_key, m4_private_key, commander_path = self.get_security_configs(build_conf, args, command) |
| 728 | + |
| 729 | + # Resolve paths |
| 730 | + tool_path = pathlib.Path(commander_path) |
| 731 | + if not tool_path.is_file(): |
| 732 | + command.die(f"Commander not found at {tool_path}. Please ensure it is installed or provide --commander-path.") |
| 733 | + |
| 734 | + private_key_path = pathlib.Path(m4_private_key) |
| 735 | + if not private_key_path.is_file(): |
| 736 | + command.die(f"Private key not found at {private_key_path}. Please ensure the path is correct.") |
| 737 | + |
| 738 | + # Build the commander signing command |
| 739 | + sign_base = [ |
| 740 | + str(tool_path), |
| 741 | + "rps", |
| 742 | + "convert", |
| 743 | + str(out_rps), |
| 744 | + "--app", str(in_bin), |
| 745 | + "--mic", m4_ota_key, |
| 746 | + "--encrypt", m4_ota_key, |
| 747 | + "--sign", str(private_key_path) |
| 748 | + ] |
| 749 | + |
| 750 | + sign_base.extend(args.tool_args) |
| 751 | + |
| 752 | + try: |
| 753 | + subprocess.check_call(sign_base, stdout=subprocess.PIPE if args.quiet else None, stderr=subprocess.PIPE if args.quiet else None) |
| 754 | + except subprocess.CalledProcessError as e: |
| 755 | + command.die(f"Commander signing failed with exit code {e.returncode}") |
| 756 | + |
| 757 | + if not args.quiet: |
| 758 | + command.inf(f"Successfully generated signed file: {out_rps}") |
0 commit comments