Skip to content

Commit

Permalink
[ci] Automatic comparison of traces
Browse files Browse the repository at this point in the history
This PR adds a new CI job to automatically compare a generated
trace to a golden reference trace. This helps to quickly
identify CI issues.

Signed-off-by: Pascal Nasahl <[email protected]>
  • Loading branch information
nasahlpa committed Oct 12, 2023
1 parent 698b81a commit 5b4b787
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 3 deletions.
13 changes: 13 additions & 0 deletions ci/azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@ jobs:
- publish: ./ci/ci_projects/opentitan_simple_aes_data/
artifact: traces
displayName: "Upload traces"
- bash: |
set -e
pushd ci
./ci_check_aes_traces.sh
./ci_trace_check/ci_compare_aes_traces.py -f ./ci_projects/opentitan_simple_aes.cwp -g ./ci_trace_check/golden_traces/aes_128_ecb_static.zip -c 0.8
popd
displayName: "Capture & check static AES traces"
- publish: ./ci/tmp/
artifact: plot_traces_aes
displayName: "Upload plot of captured AES traces."
- publish: ./ci/ci_projects/opentitan_simple_aes.zip
artifact: project_traces_aes
displayName: "Upload project of captured AES traces."
8 changes: 5 additions & 3 deletions ci/ci_capture_aes_cw310.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ capture:
lfsr_seed: 0xdeadbeef
batch_prng_seed: 0
scope_gain: 31.5
num_traces: 1000
num_traces: 100
project_name: ci_projects/opentitan_simple_aes
waverunner_ip: 192.168.1.228
project_export: True
project_export_filename: ci_projects/opentitan_simple_aes.zip
plot_capture:
show: False
show: True
num_traces: 100
trace_image_filename: null
trace_image_filename: ci_projects/sample_traces_aes.html
25 changes: 25 additions & 0 deletions ci/ci_check_aes_traces.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash

# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

# Simple script to test AES capture.
mkdir -p tmp

# AES
MODE="aes"
BOARD=cw310
declare -A aes_test_list
aes_test_list["aes-static"]=100

ARGS="--force-program-bitstream"
for test in ${!aes_test_list[@]}; do
echo Testing ${test} on CW310 - `date`
NUM_TRACES=${aes_test_list[${test}]}
../cw/capture.py --cfg-file ci_capture_aes_cw310.yaml capture ${test} \
--num-traces ${NUM_TRACES} ${ARGS} &>> "tmp/test_capture.log"

mv ./ci_projects/sample_traces_${MODE}.html tmp/${test}_traces.html
ARGS=""
done
90 changes: 90 additions & 0 deletions ci/ci_trace_check/ci_compare_aes_traces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
import argparse
import sys

import chipwhisperer as cw
import numpy as np
import scipy.stats


def analyze_traces(file_proj, file_gold_proj, corr_coeff) -> bool:
"""Performs a correlation between golden and new traces.
This function:
- Computes the mean of the golden and new traces,
- Computes the pearson coefficient of these means,
- Compares the coefficient with the user provided threshold.
Args:
file_proj: The new Chipwhisperer project file.
file_gold_proj: The golden Chipwhisperer project file.
corr_coeff: User defined correlation threshold.
Returns:
True if trace comparison succeeds, False otherwise.
"""
# Open the current project
proj_curr = cw.open_project(file_proj)
# Calculate mean of new traces
curr_trace = np.mean(proj_curr.waves, axis=0)

# Import the golden project
proj_gold = cw.import_project(file_gold_proj)
# Calculate mean of golden traces
gold_trace = np.mean(proj_gold.waves, axis=0)

# Pearson correlation: golden trace vs. mean of new traces
calc_coeff = scipy.stats.pearsonr(gold_trace, curr_trace).correlation
print(f'Correlation={round(calc_coeff,3)},')
# Fail / pass
if calc_coeff < corr_coeff:
return False
else:
return True


def parse_args():
"""Parses command-line arguments."""
parser = argparse.ArgumentParser(
description="""Calculate Pearson correlation between golden
traces and captured traces. Failes when correlation
coefficient is below user threshold."""
)
parser.add_argument(
"-f",
"--file_proj",
required=True,
help="chipwhisperer project file"
)
parser.add_argument(
"-g",
"--file_gold_proj",
required=True,
help="chipwhisperergolden project file"
)
parser.add_argument(
"-c",
"--corr_coeff",
type=float,
required=True,
help="specifies the correlation coefficient threshold"
)
return parser.parse_args()


def main() -> int:
"""Parses command-line arguments and TODO"""
args = parse_args()

if analyze_traces(**vars(args)):
print('Traces OK.')
else:
print('Traces correlation below threshold.')
sys.exit(1)


if __name__ == "__main__":
main()
Binary file not shown.
41 changes: 41 additions & 0 deletions cw/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,47 @@ def capture_loop(trace_gen, ot, capture_cfg, device_cfg):
def capture_end(cfg):
if cfg["plot_capture"]["show"]:
plot_results(cfg["plot_capture"], cfg["capture"]["project_name"])
if cfg["capture"]["project_export"]:
project = cw.open_project(cfg["capture"]["project_name"])
project.export(cfg["capture"]["project_export_filename"])
project.close(save=False)


def capture_aes_static(ot):
"""A generator for capturing AES traces for fixed key and test.
Args:
ot: Initialized OpenTitan target.
"""
key = bytearray([0x81, 0x1E, 0x37, 0x31, 0xB0, 0x12, 0x0A, 0x78,
0x42, 0x78, 0x1E, 0x22, 0xB2, 0x5C, 0xDD, 0xF9])
text = bytearray([0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA])

tqdm.write(f'Fixed key: {binascii.b2a_hex(bytes(key))}')

while True:
cipher = AES.new(bytes(key), AES.MODE_ECB)
ret = cw.capture_trace(ot.scope, ot.target, text, key, ack=False, as_int=True)
if not ret:
raise RuntimeError('Capture failed.')
expected = binascii.b2a_hex(cipher.encrypt(bytes(text)))
got = binascii.b2a_hex(ret.textout)
if got != expected:
raise RuntimeError(f'Bad ciphertext: {got} != {expected}.')
yield ret


@app_capture.command()
def aes_static(ctx: typer.Context,
force_program_bitstream: bool = opt_force_program_bitstream,
num_traces: int = opt_num_traces,
plot_traces: int = opt_plot_traces):
"""Capture AES traces from a target that runs the `aes_serial` program."""
capture_init(ctx, force_program_bitstream, num_traces, plot_traces)
capture_loop(capture_aes_static(ctx.obj.ot), ctx.obj.ot,
ctx.obj.cfg["capture"], ctx.obj.cfg["device"])
capture_end(ctx.obj.cfg)


def capture_aes_random(ot, ktp):
Expand Down

0 comments on commit 5b4b787

Please sign in to comment.