1# Copyright (c) 2023 Nordic Semiconductor ASA 2# 3# SPDX-License-Identifier: Apache-2.0 4from __future__ import annotations 5 6import pytest 7import logging 8 9from pathlib import Path 10from twister_harness import DeviceAdapter, Shell, MCUmgr 11from west_sign_wrapper import west_sign_with_imgtool 12from utils import ( 13 find_in_config, 14 match_lines, 15 match_no_lines, 16 check_with_shell_command, 17 check_with_mcumgr_command, 18) 19 20logger = logging.getLogger(__name__) 21 22 23def create_signed_image(build_dir: Path, app_build_dir: Path, version: str) -> Path: 24 image_to_test = Path(build_dir) / 'test_{}.signed.bin'.format( 25 version.replace('.', '_').replace('+', '_')) 26 origin_key_file = find_in_config( 27 Path(build_dir) / 'mcuboot' / 'zephyr' / '.config', 28 'CONFIG_BOOT_SIGNATURE_KEY_FILE' 29 ) 30 west_sign_with_imgtool( 31 build_dir=Path(app_build_dir), 32 output_bin=image_to_test, 33 key_file=Path(origin_key_file), 34 version=version 35 ) 36 assert image_to_test.is_file() 37 return image_to_test 38 39 40def clear_buffer(dut: DeviceAdapter) -> None: 41 disconnect = False 42 if not dut.is_device_connected(): 43 dut.connect() 44 disconnect = True 45 dut.clear_buffer() 46 if disconnect: 47 dut.disconnect() 48 49 50def test_upgrade_with_confirm(dut: DeviceAdapter, shell: Shell, mcumgr: MCUmgr): 51 """ 52 Verify that the application can be updated 53 1) Device flashed with MCUboot and an application that contains SMP server 54 2) Prepare an update of an application containing the SMP server 55 3) Upload the application update to slot 1 using mcumgr 56 4) Flag the application update in slot 1 as 'pending' by using mcumgr 'test' 57 5) Restart the device, verify that swapping process is initiated 58 6) Verify that the updated application is booted 59 7) Confirm the image using mcumgr 60 8) Restart the device, and verify that the new application is still booted 61 """ 62 logger.info('Prepare upgrade image') 63 new_version = '0.0.2+0' 64 image_to_test = create_signed_image(dut.device_config.build_dir, 65 dut.device_config.app_build_dir, new_version) 66 67 logger.info('Upload image with mcumgr') 68 dut.disconnect() 69 mcumgr.image_upload(image_to_test) 70 71 logger.info('Test uploaded APP image') 72 second_hash = mcumgr.get_hash_to_test() 73 mcumgr.image_test(second_hash) 74 clear_buffer(dut) 75 mcumgr.reset_device() 76 77 dut.connect() 78 output = dut.readlines_until('Launching primary slot application') 79 match_lines(output, [ 80 'Swap type: test', 81 'Starting swap using move algorithm' 82 ]) 83 logger.info('Verify new APP is booted') 84 check_with_shell_command(shell, new_version, swap_type='test') 85 dut.disconnect() 86 check_with_mcumgr_command(mcumgr, new_version) 87 88 logger.info('Confirm the image') 89 mcumgr.image_confirm(second_hash) 90 mcumgr.reset_device() 91 92 dut.connect() 93 output = dut.readlines_until('Launching primary slot application') 94 match_no_lines(output, [ 95 'Starting swap using move algorithm' 96 ]) 97 logger.info('Verify new APP is still booted') 98 check_with_shell_command(shell, new_version) 99 100 101def test_upgrade_with_revert(dut: DeviceAdapter, shell: Shell, mcumgr: MCUmgr): 102 """ 103 Verify that MCUboot will roll back an image that is not confirmed 104 1) Device flashed with MCUboot and an application that contains SMP server 105 2) Prepare an update of an application containing the SMP server 106 3) Upload the application update to slot 1 using mcumgr 107 4) Flag the application update in slot 1 as 'pending' by using mcumgr 'test' 108 5) Restart the device, verify that swapping process is initiated 109 6) Verify that the updated application is booted 110 7) Reset the device without confirming the image 111 8) Verify that MCUboot reverts update 112 """ 113 origin_version = find_in_config( 114 Path(dut.device_config.app_build_dir) / 'zephyr' / '.config', 115 'CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION' 116 ) 117 logger.info('Prepare upgrade image') 118 new_version = '0.0.3+0' 119 image_to_test = create_signed_image(dut.device_config.build_dir, 120 dut.device_config.app_build_dir, new_version) 121 122 logger.info('Upload image with mcumgr') 123 dut.disconnect() 124 mcumgr.image_upload(image_to_test) 125 126 logger.info('Test uploaded APP image') 127 second_hash = mcumgr.get_hash_to_test() 128 mcumgr.image_test(second_hash) 129 clear_buffer(dut) 130 mcumgr.reset_device() 131 132 dut.connect() 133 output = dut.readlines_until('Launching primary slot application') 134 match_lines(output, [ 135 'Swap type: test', 136 'Starting swap using move algorithm' 137 ]) 138 logger.info('Verify new APP is booted') 139 check_with_shell_command(shell, new_version, swap_type='test') 140 dut.disconnect() 141 check_with_mcumgr_command(mcumgr, new_version) 142 143 logger.info('Revert images') 144 mcumgr.reset_device() 145 146 dut.connect() 147 output = dut.readlines_until('Launching primary slot application') 148 match_lines(output, [ 149 'Swap type: revert', 150 'Starting swap using move algorithm' 151 ]) 152 logger.info('Verify that MCUboot reverts update') 153 check_with_shell_command(shell, origin_version) 154 155 156@pytest.mark.parametrize( 157 'key_file', [None, 'root-ec-p256.pem'], 158 ids=[ 159 'no_key', 160 'invalid_key' 161 ]) 162def test_upgrade_signature(dut: DeviceAdapter, shell: Shell, mcumgr: MCUmgr, key_file): 163 """ 164 Verify that the application is not updated when app is not signed or signed with invalid key 165 1) Device flashed with MCUboot and an application that contains SMP server 166 2) Prepare an update of an application containing the SMP server that has 167 been signed: 168 a) without any key 169 b) with a different key than MCUboot was compiled with 170 3) Upload the application update to slot 1 using mcumgr 171 4) Flag the application update in slot 1 as 'pending' by using mcumgr 'test' 172 5) Restart the device, verify that swap is not started 173 """ 174 if key_file: 175 origin_key_file = find_in_config( 176 Path(dut.device_config.build_dir) / 'mcuboot' / 'zephyr' / '.config', 177 'CONFIG_BOOT_SIGNATURE_KEY_FILE' 178 ).strip('"\'') 179 key_file = Path(origin_key_file).parent / key_file 180 assert key_file.is_file() 181 assert not key_file.samefile(origin_key_file) 182 image_to_test = Path(dut.device_config.build_dir) / 'test_invalid_key.bin' 183 logger.info('Sign second image with an invalid key') 184 else: 185 image_to_test = Path(dut.device_config.build_dir) / 'test_no_key.bin' 186 logger.info('Sign second imagewith no key') 187 188 west_sign_with_imgtool( 189 build_dir=Path(dut.device_config.app_build_dir), 190 output_bin=image_to_test, 191 key_file=key_file, 192 version='0.0.3+4' # must differ from the origin version, if not then hash is not updated 193 ) 194 assert image_to_test.is_file() 195 196 logger.info('Upload image with mcumgr') 197 dut.disconnect() 198 mcumgr.image_upload(image_to_test) 199 200 logger.info('Test uploaded APP image') 201 second_hash = mcumgr.get_hash_to_test() 202 mcumgr.image_test(second_hash) 203 204 logger.info('Verify that swap is not started') 205 clear_buffer(dut) 206 mcumgr.reset_device() 207 208 dut.connect() 209 output = dut.readlines_until('Launching primary slot application') 210 match_no_lines(output, ['Starting swap using move algorithm']) 211 match_lines(output, ['Image in the secondary slot is not valid']) 212