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