Skip to content

Commit 8b530f0

Browse files
committed
Add L9_e2_tests
1 parent d2eb1fb commit 8b530f0

3 files changed

Lines changed: 306 additions & 3 deletions

File tree

l9/l9_e1_test/tools/dfu_update.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ echo ">>> Step 4: Confirm running image"
9191
python3 "$MCUBOOT_MGR" -p "$PORT" confirm --slot 0
9292
echo
9393

94+
9495
# Step 5: Print image info after update
9596
echo ">>> Step 5: Image info after update"
9697
python3 "$MCUBOOT_MGR" -p "$PORT" info
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
set -euo pipefail
1414

1515
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16-
PROJECT_DIR="$(cd "$SCRIPT_DIR/../../l9_e1_sol" && pwd)"
17-
MCUBOOT_MGR="$SCRIPT_DIR/mcuboot_mgr.py"
18-
DFU_UPDATE="$SCRIPT_DIR/dfu_update.sh"
16+
TOOLS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../../../l9_e1_sol" && pwd)"
18+
MCUBOOT_MGR="$TOOLS_DIR/mcuboot_mgr.py"
19+
DFU_UPDATE="$TOOLS_DIR/dfu_update.sh"
1920

2021
MAIN_C="$PROJECT_DIR/src/main.c"
2122
VERSION_FILE="$PROJECT_DIR/VERSION"

l9/l9_e2_sol/test/dfu_test.sh

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
#!/bin/bash
2+
# MCUboot DFU security test (self-contained).
3+
#
4+
# Validates that MCUboot correctly accepts/rejects firmware based on signing key:
5+
# Phase 1: Build & flash initial image (valid key, SLEEP=1000ms)
6+
# Phase 2: DFU with valid key (SLEEP=200ms) — must succeed
7+
# Phase 3: DFU with invalid key (SLEEP=1000ms) — must be rejected
8+
#
9+
# Depends only on mcuboot_mgr.py
10+
# Key type auto-detected from board name: nrf54* → ed25519, else → ecdsa.
11+
#
12+
# Usage:
13+
# ./dfu_test.sh --port /dev/ttyACM1 --board nrf54lm20dk/nrf54lm20a/cpuapp
14+
# ./dfu_test.sh --port /dev/ttyACM8 --board nrf52840dk/nrf52840 --snr 683165209
15+
# ./dfu_test.sh --port /dev/ttyACM4 --board nrf9151dk/nrf9151/ns --wait 20 --from-phase 3
16+
# ./dfu_test.sh --port /dev/ttyACM1 --board nrf54l15dk/nrf54l15/cpuapp --mgr /path/to/mcuboot_mgr.py
17+
18+
set -euo pipefail
19+
20+
# ── Paths ──────────────────────────────────────────────────────
21+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22+
APP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
23+
MAIN_C="$APP_DIR/src/main.c"
24+
25+
# ── Defaults ───────────────────────────────────────────────────
26+
PORT=""
27+
BOARD=""
28+
KEY_TYPE=""
29+
SNR=""
30+
WAIT=5
31+
NCS_VERSION="v3.4.0-rc2"
32+
FROM_PHASE=1
33+
MCUBOOT_MGR=""
34+
35+
# ── Usage ──────────────────────────────────────────────────────
36+
usage() {
37+
cat <<EOF
38+
Usage: $(basename "$0") [OPTIONS]
39+
40+
Options:
41+
--port PORT Serial port for SMP (required)
42+
--board TARGET Board target (required)
43+
--key-type TYPE ed25519 or ecdsa (auto-detected from board name)
44+
--snr SERIAL J-Link serial number (for multi-board setups)
45+
--wait SECONDS Wait after reset for boot (default: $WAIT)
46+
--ncs-version VER NCS version (default: $NCS_VERSION)
47+
--from-phase N Resume from phase 1, 2, or 3 (default: 1)
48+
--mgr PATH Path to mcuboot_mgr.py (auto-detected if omitted)
49+
-h, --help Show this help
50+
EOF
51+
exit 1
52+
}
53+
54+
# ── Parse args ─────────────────────────────────────────────────
55+
while [[ $# -gt 0 ]]; do
56+
case "$1" in
57+
--port) PORT="$2"; shift 2 ;;
58+
--board) BOARD="$2"; shift 2 ;;
59+
--key-type) KEY_TYPE="$2"; shift 2 ;;
60+
--snr) SNR="$2"; shift 2 ;;
61+
--wait) WAIT="$2"; shift 2 ;;
62+
--ncs-version) NCS_VERSION="$2"; shift 2 ;;
63+
--from-phase) FROM_PHASE="$2"; shift 2 ;;
64+
--mgr) MCUBOOT_MGR="$2"; shift 2 ;;
65+
-h|--help) usage ;;
66+
*) echo "ERROR: Unknown option: $1"; usage ;;
67+
esac
68+
done
69+
70+
[[ -z "$PORT" ]] && { echo "ERROR: --port is required"; usage; }
71+
[[ -z "$BOARD" ]] && { echo "ERROR: --board is required"; usage; }
72+
[[ "$FROM_PHASE" =~ ^[123]$ ]] || { echo "ERROR: --from-phase must be 1, 2, or 3"; usage; }
73+
74+
# Auto-detect mcuboot_mgr.py if not specified
75+
if [[ -z "$MCUBOOT_MGR" ]]; then
76+
MCUBOOT_MGR="$(cd "$APP_DIR/../l9_e1_test/tools" 2>/dev/null && pwd)/mcuboot_mgr.py"
77+
fi
78+
[[ -f "$MCUBOOT_MGR" ]] || { echo "ERROR: mcuboot_mgr.py not found: $MCUBOOT_MGR"; exit 1; }
79+
80+
# ── Key type / files ──────────────────────────────────────────
81+
if [[ -z "$KEY_TYPE" ]]; then
82+
[[ "$BOARD" == *nrf54* ]] && KEY_TYPE="ed25519" || KEY_TYPE="ecdsa"
83+
fi
84+
85+
case "$KEY_TYPE" in
86+
ed25519)
87+
VALID_KEY="$APP_DIR/ed_ci_key.pem"
88+
INVALID_KEY="$APP_DIR/ed_ci_key_invalid.pem"
89+
SIG_ED25519="y"; SIG_ECDSA="n"
90+
EXTRA_BUILD_ARGS=("-DSB_CONFIG_MCUBOOT_GENERATE_DEFAULT_KEY_FILE=y")
91+
;;
92+
ecdsa)
93+
VALID_KEY="$APP_DIR/ecdsa_ci_key.pem"
94+
INVALID_KEY="$APP_DIR/ecdsa_ci_key_invalid.pem"
95+
SIG_ED25519="n"; SIG_ECDSA="y"
96+
EXTRA_BUILD_ARGS=()
97+
;;
98+
*) echo "ERROR: Invalid key type '$KEY_TYPE'"; exit 1 ;;
99+
esac
100+
101+
for f in "$VALID_KEY" "$INVALID_KEY"; do
102+
[[ -f "$f" ]] || { echo "ERROR: Key not found: $f"; exit 1; }
103+
done
104+
105+
# ── NCS / toolchain setup ─────────────────────────────────────
106+
NCS_ROOT="$HOME/ncs/$NCS_VERSION"
107+
[[ -d "$NCS_ROOT" ]] || { echo "ERROR: NCS not found at $NCS_ROOT"; exit 1; }
108+
109+
TC_BUNDLE=$(python3 -c "
110+
import json, sys
111+
for e in json.load(open('$HOME/ncs/toolchains/toolchains.json')):
112+
for t in e.get('toolchains', []):
113+
if '$NCS_VERSION' in t.get('ncs_versions', []):
114+
print(t['identifier']['bundle_id']); sys.exit(0)
115+
sys.exit(1)
116+
") || { echo "ERROR: Toolchain not found for $NCS_VERSION"; exit 1; }
117+
118+
TC="$HOME/ncs/toolchains/$TC_BUNDLE"
119+
export PATH="$TC/bin:$TC/usr/bin:$TC/usr/local/bin:$TC/opt/bin:$TC/opt/nanopb/generator-bin:$TC/nrfutil/bin:$TC/opt/zephyr-sdk/gnu/arm-zephyr-eabi/bin:$TC/opt/zephyr-sdk/gnu/riscv64-zephyr-elf/bin:$PATH"
120+
export LD_LIBRARY_PATH="$TC/lib:$TC/lib/x86_64-linux-gnu:$TC/usr/local/lib"
121+
export GIT_EXEC_PATH="$TC/usr/local/libexec/git-core"
122+
export GIT_TEMPLATE_DIR="$TC/usr/local/share/git-core/templates"
123+
export PYTHONHOME="$TC/usr/local"
124+
export PYTHONPATH="$TC/usr/local/lib/python3.12:$TC/usr/local/lib/python3.12/site-packages"
125+
export NRFUTIL_HOME="$TC/nrfutil/home"
126+
export ZEPHYR_TOOLCHAIN_VARIANT="zephyr/gnu"
127+
export ZEPHYR_SDK_INSTALL_DIR="$TC/opt/zephyr-sdk"
128+
export ZEPHYR_BASE="$NCS_ROOT/zephyr"
129+
130+
BUILD_DIR="$APP_DIR/build"
131+
SIGNED_BIN="$BUILD_DIR/l9_e2_sol/zephyr/zephyr.signed.bin"
132+
BUILD_STAMP="$BUILD_DIR/.build_stamp"
133+
FLASH_ARGS=(); [[ -n "$SNR" ]] && FLASH_ARGS+=(--snr "$SNR")
134+
135+
# ── Helpers ────────────────────────────────────────────────────
136+
137+
# Run with system Python (smpmgr needs it, toolchain Python breaks it)
138+
mgr() { env -u PYTHONHOME -u PYTHONPATH python3 "$MCUBOOT_MGR" -p "$PORT" "$@"; }
139+
140+
# Get slot 0 hash from device
141+
get_hash() { mgr info 2>/dev/null | grep "Slot 0 hash" | awk '{print $4}'; }
142+
143+
# Patch SLEEP_TIME_MS in source
144+
patch_sleep() {
145+
sed -i "s/#define SLEEP_TIME_MS.*/#define SLEEP_TIME_MS $1/" "$MAIN_C"
146+
echo " SLEEP_TIME_MS=$1"
147+
}
148+
149+
# Build (skip if stamp matches)
150+
do_build() {
151+
local key_file="$1" label="$2" sleep_ms="$3"
152+
local stamp="board=$BOARD key=$(basename "$key_file") key_type=$KEY_TYPE sleep=$sleep_ms"
153+
154+
echo " Key: $label ($(basename "$key_file"))"
155+
if [[ -f "$BUILD_STAMP" && -f "$SIGNED_BIN" ]] && [[ "$(cat "$BUILD_STAMP")" == "$stamp" ]]; then
156+
echo " Build cache hit — skipping rebuild"
157+
return 0
158+
fi
159+
160+
echo " Building..."
161+
local build_log="/tmp/dfu_build_$$.log"
162+
cd "$NCS_ROOT"
163+
west build -b "$BOARD" "$APP_DIR" -d "$BUILD_DIR" --sysbuild --pristine \
164+
-- \
165+
"-DSB_CONFIG_BOOT_SIGNATURE_TYPE_ED25519=$SIG_ED25519" \
166+
"-DSB_CONFIG_BOOT_SIGNATURE_TYPE_ECDSA_P256=$SIG_ECDSA" \
167+
"-DSB_CONFIG_BOOT_SIGNATURE_KEY_FILE=\"$key_file\"" \
168+
"${EXTRA_BUILD_ARGS[@]}" \
169+
2>&1 | tee "$build_log" | tail -5
170+
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
171+
echo " BUILD FAILED — last 30 lines:"
172+
tail -30 "$build_log"
173+
exit 1
174+
fi
175+
echo "$stamp" > "$BUILD_STAMP"
176+
}
177+
178+
# DFU: upload → mark test → reset → wait → confirm
179+
do_dfu() {
180+
echo " Uploading image..."
181+
mgr upload "$SIGNED_BIN" --test
182+
echo
183+
echo " Resetting device..."
184+
mgr reset
185+
echo " Waiting ${WAIT}s for boot..."
186+
sleep "$WAIT"
187+
echo
188+
echo " Confirming running image..."
189+
mgr confirm --slot 0
190+
}
191+
192+
# ── Print header ───────────────────────────────────────────────
193+
PASS=0; FAIL=0
194+
195+
echo "============================================================"
196+
echo " MCUboot DFU Security Test"
197+
echo "============================================================"
198+
echo " Board: $BOARD"
199+
echo " Port: $PORT"
200+
echo " Key type: $KEY_TYPE"
201+
echo " NCS: $NCS_ROOT (toolchain: $TC_BUNDLE)"
202+
[[ $FROM_PHASE -gt 1 ]] && echo " Resuming from phase $FROM_PHASE"
203+
echo "============================================================"
204+
echo
205+
206+
# ── PHASE 1: Flash initial image (valid key, 1000ms) ──────────
207+
if [[ $FROM_PHASE -le 1 ]]; then
208+
echo ">>> PHASE 1: Build & flash (valid key, SLEEP=1000ms)"
209+
echo
210+
patch_sleep 1000
211+
do_build "$VALID_KEY" "VALID" 1000
212+
echo
213+
echo " Flashing..."
214+
west flash -d "$BUILD_DIR" --erase --recover "${FLASH_ARGS[@]}" 2>&1 | tail -5
215+
echo " Waiting ${WAIT}s for boot..."
216+
sleep "$WAIT"
217+
echo
218+
echo " Slot info:"
219+
mgr info
220+
echo
221+
else
222+
echo ">>> PHASE 1: SKIPPED (--from-phase $FROM_PHASE)"
223+
echo
224+
fi
225+
226+
INITIAL_HASH=$(get_hash)
227+
228+
# ── PHASE 2: DFU with valid key (200ms) → should succeed ──────
229+
if [[ $FROM_PHASE -le 2 ]]; then
230+
echo ">>> PHASE 2: DFU with VALID key (SLEEP=200ms)"
231+
echo
232+
patch_sleep 200
233+
do_build "$VALID_KEY" "VALID" 200
234+
echo
235+
do_dfu
236+
echo
237+
238+
PHASE2_HASH=$(get_hash)
239+
if [[ -n "$PHASE2_HASH" && "$PHASE2_HASH" != "$INITIAL_HASH" ]]; then
240+
echo " RESULT: PASS — hash changed (valid-key DFU accepted)"
241+
PASS=$((PASS + 1))
242+
else
243+
echo " RESULT: FAIL — hash unchanged"
244+
FAIL=$((FAIL + 1))
245+
fi
246+
echo
247+
else
248+
echo ">>> PHASE 2: SKIPPED (--from-phase $FROM_PHASE)"
249+
echo
250+
fi
251+
252+
# ── PHASE 3: DFU with invalid key (1000ms) → must be rejected ─
253+
if [[ $FROM_PHASE -le 3 ]]; then
254+
echo ">>> PHASE 3: DFU with INVALID key (SLEEP=1000ms)"
255+
echo
256+
[[ $FROM_PHASE -eq 3 ]] && PHASE2_HASH="$INITIAL_HASH"
257+
258+
patch_sleep 1000
259+
do_build "$INVALID_KEY" "INVALID" 1000
260+
echo
261+
262+
echo " Uploading image..."
263+
mgr upload "$SIGNED_BIN" --test
264+
echo
265+
echo " Resetting device (expecting MCUboot to reject)..."
266+
mgr reset
267+
echo " Waiting ${WAIT}s for boot + swap-back..."
268+
sleep "$WAIT"
269+
echo
270+
271+
# Confirm will likely fail (image reverted) — that's expected
272+
mgr confirm --slot 0 || true
273+
echo " Waiting extra ${WAIT}s for recovery..."
274+
sleep "$WAIT"
275+
echo
276+
277+
PHASE3_HASH=$(get_hash)
278+
if [[ -n "$PHASE3_HASH" && "$PHASE3_HASH" == "$PHASE2_HASH" ]]; then
279+
echo " RESULT: PASS — hash unchanged (invalid-key DFU rejected)"
280+
PASS=$((PASS + 1))
281+
else
282+
echo " RESULT: FAIL — hash changed (invalid-key DFU accepted!)"
283+
FAIL=$((FAIL + 1))
284+
fi
285+
echo
286+
fi
287+
288+
# ── Cleanup & summary ─────────────────────────────────────────
289+
patch_sleep 1000
290+
291+
echo "============================================================"
292+
echo " Results: $PASS / $((PASS + FAIL)) passed"
293+
if [[ $FAIL -gt 0 ]]; then
294+
echo " Status: FAILED"
295+
echo "============================================================"
296+
exit 1
297+
else
298+
echo " Status: ALL PASSED"
299+
echo "============================================================"
300+
exit 0
301+
fi

0 commit comments

Comments
 (0)