aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJán Jančár2023-07-19 15:06:03 +0200
committerGitHub2023-07-19 15:06:03 +0200
commit0602b39001ca8a8ea55df3990294de19cfd2cafb (patch)
tree5883f9e9e3879cbd6756ac8974b30c8ad0f959ce
parent324f68e7930cde44b13e791f35423fcb33b8aa6c (diff)
parent533c5766441ff6a9355441874781645d23b61e90 (diff)
downloadpyecsca-codegen-0602b39001ca8a8ea55df3990294de19cfd2cafb.tar.gz
pyecsca-codegen-0602b39001ca8a8ea55df3990294de19cfd2cafb.tar.zst
pyecsca-codegen-0602b39001ca8a8ea55df3990294de19cfd2cafb.zip
Merge pull request #2 from andrr3j/feat/simulator
Add leakage simulation via Rainbow
-rw-r--r--.github/workflows/test.yml4
-rw-r--r--Makefile2
-rw-r--r--pyecsca/codegen/client.py132
-rw-r--r--pyecsca/codegen/templates/main.c4
-rw-r--r--setup.py3
-rw-r--r--test/test_impl.py2
-rw-r--r--test/test_simulator.py194
7 files changed, 335 insertions, 6 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 82ccd60..597153d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,7 +4,7 @@ on: [push, pull_request]
env:
LLVM_CONFIG: /usr/bin/llvm-config-10
- OTHER_PACKAGES: swig gcc libpcsclite-dev llvm-10 libllvm10 llvm-10-dev
+ OTHER_PACKAGES: swig gcc libpcsclite-dev llvm-10 libllvm10 llvm-10-dev gcc-arm-none-eabi binutils-arm-none-eabi libnewlib-arm-none-eabi libnewlib-nano-arm-none-eabi
jobs:
test:
@@ -36,7 +36,7 @@ jobs:
sudo apt-get install -y $OTHER_PACKAGES
- name: Build libtommath
run: |
- cd ext && make host && cd ..
+ cd ext && make host stm32f3 && cd ..
- name: Install dependencies
run: |
pip install -U pip setuptools wheel
diff --git a/Makefile b/Makefile
index 42858a7..790a977 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-TESTS = test_builder test_client test_render test_impl
+TESTS = test_builder test_client test_render test_impl test_simulator
test:
nose2 -s test -A !slow -C -v ${TESTS}
diff --git a/pyecsca/codegen/client.py b/pyecsca/codegen/client.py
index 22e6634..eb56276 100644
--- a/pyecsca/codegen/client.py
+++ b/pyecsca/codegen/client.py
@@ -16,11 +16,14 @@ from pyecsca.ec.mod import Mod
from pyecsca.ec.model import CurveModel
from pyecsca.ec.params import DomainParameters, get_params
from pyecsca.ec.point import Point, InfinityPoint
-from pyecsca.sca.target import (SimpleSerialTarget, ChipWhispererTarget, BinaryTarget, Flashable,
+from pyecsca.sca.target import (Target, SimpleSerialTarget, ChipWhispererTarget, BinaryTarget, Flashable,
SimpleSerialMessage as SMessage)
from .common import wrap_enum, Platform, get_model, get_coords
+from rainbow.devices import rainbow_stm32f215
+from rainbow import TraceConfig, Print
+
class Triggers(IntFlag):
"""
@@ -186,6 +189,133 @@ def cmd_debug() -> str:
return "d"
+class SimulatorTarget(Target):
+
+ simulator: rainbow_stm32f215
+ result: list
+ model: CurveModel
+ coords: CoordinateModel
+ seed: Optional[bytes]
+ params: Optional[DomainParameters]
+ privkey: Optional[int]
+ pubkey: Optional[Point]
+ trace: list
+
+ def __init__(self, model: CurveModel, coords: CoordinateModel, print_config: Print = Print(0),
+ trace_config: TraceConfig = TraceConfig(), allow_breakpoints: bool = False):
+ super().__init__()
+ self.simulator = rainbow_stm32f215(print_config=print_config, trace_config=trace_config,
+ allow_stubs=True, allow_breakpoints=allow_breakpoints)
+ self.result = []
+ self.trace = []
+ self.model = model
+ self.coords = coords
+ self.seed = None
+ self.params = None
+ self.privkey = None
+ self.pubkey = None
+
+ def __simulate(self, command: str, function: str) -> None:
+ data = unhexlify(command[1:])
+ length = len(data)
+ data_adress = 0xDEAD0000
+ self.simulator[data_adress] = data
+ self.simulator['r0'] = data_adress
+ self.simulator['r1'] = length
+ self.simulator.start(self.simulator.functions[function] | 1, 0)
+ self.trace.extend(self.simulator.trace)
+ self.simulator.reset()
+
+ def hook_result(self, simulator) -> None:
+ self.result.append(simulator['r0'])
+ self.result.append(simulator['r1'])
+ self.result.append(simulator['r2'])
+
+ def connect(self, **kwargs) -> None:
+ self.simulator.load(kwargs["binary"])
+ self.simulator.setup()
+ self.simulator.hook_bypass("simpleserial_put", self.hook_result)
+ self.simulator.start(self.simulator.functions['init_implementation'] | 1, 0)
+ self.simulator.reset()
+
+ def set_params(self, params: DomainParameters) -> None:
+ command = cmd_set_params(params)
+ self.__simulate(command, 'cmd_set_params')
+ self.params = params
+
+ def scalar_mult(self, scalar: int, point: Point) -> Point:
+ command = cmd_scalar_mult(scalar, point)
+ self.__simulate(command, 'cmd_scalar_mult')
+ res_adress = self.result[2]
+ point_length = self.result[1] // len(self.coords.variables)
+ params = {var: Mod(int.from_bytes(self.simulator[res_adress + i * point_length:
+ res_adress + (i + 1) * point_length], 'big'),
+ self.params.curve.prime)
+ for i, var in enumerate(self.coords.variables)}
+ self.result = []
+ return Point(self.coords, **params)
+
+ def init_prng(self, seed: bytes) -> None:
+ command = cmd_init_prng(seed)
+ self.__simulate(command, 'cmd_init_prng')
+ self.seed = seed
+
+ def generate(self) -> Tuple[int, Point]:
+ command = cmd_generate()
+ self.__simulate(command, 'cmd_generate')
+ priv = int.from_bytes(self.simulator[self.result[2]:self.result[2] + self.result[1]], 'big')
+ pub_x = int.from_bytes(self.simulator[self.result[5]:self.result[5] + self.result[4] // 2], 'big')
+ pub_y = int.from_bytes(self.simulator[self.result[5] + self.result[4] // 2:self.result[5] + self.result[4]] ,'big')
+ self.result = []
+ return priv, Point(AffineCoordinateModel(self.model), x = Mod(pub_x, self.params.curve.prime),
+ y = Mod(pub_y, self.params.curve.prime))
+
+ def set_privkey(self, privkey: int) -> None:
+ command = cmd_set_privkey(privkey)
+ self.__simulate(command, 'cmd_set_privkey')
+ self.privkey = privkey
+
+ def set_pubkey(self, pubkey: Point) -> None:
+ command = cmd_set_pubkey(pubkey)
+ self.__simulate(command, 'cmd_set_pubkey')
+ self.pubkey = pubkey
+
+ def ecdh(self, other_pubkey: Point) -> bytes:
+ command = cmd_ecdh(other_pubkey)
+ self.__simulate(command, 'cmd_ecdh')
+ shared_secret = self.simulator[self.result[2]:self.result[2] + self.result[1]]
+ self.result = []
+ return shared_secret
+
+ def ecdsa_sign(self, data: bytes) -> bytes:
+ command = cmd_ecdsa_sign(data)
+ self.__simulate(command, 'cmd_ecdsa_sign')
+ signature = self.simulator[self.result[2]:self.result[2] + self.result[1]]
+ self.result = []
+ return signature
+
+ def ecdsa_verify(self, data: bytes, signature: bytes) -> bool:
+ command = cmd_ecdsa_verify(data, signature)
+ self.__simulate(command, 'cmd_ecdsa_verify')
+ res = self.simulator[self.result[2]:self.result[2] + self.result[1]]
+ self.result = []
+ return bool(int.from_bytes(res, 'big'))
+
+ def set_strigger(self):
+ pass
+
+ def debug(self) -> Tuple[str, str]:
+ return self.model.shortname, self.coords.name
+
+ def quit(self):
+ pass
+
+ def disconnect(self):
+ self.simulator.start(self.simulator.functions['deinit'] | 1, 0)
+ self.simulator.reset()
+
+
+
class ImplTarget(SimpleSerialTarget):
"""
A target that is based on an implementation built by pyecsca-codegen.
diff --git a/pyecsca/codegen/templates/main.c b/pyecsca/codegen/templates/main.c
index af82603..4d50cc2 100644
--- a/pyecsca/codegen/templates/main.c
+++ b/pyecsca/codegen/templates/main.c
@@ -568,6 +568,10 @@ __attribute__((noinline)) void init(void) {
init_uart();
trigger_setup();
+ init_implementation();
+}
+
+__attribute__((noinline)) void init_implementation(void) {
// Initialize some components that preallocate stuff.
prng_init();
formulas_init();
diff --git a/setup.py b/setup.py
index 78afd60..e486193 100644
--- a/setup.py
+++ b/setup.py
@@ -39,7 +39,8 @@ setup(
"fastdtw",
"asn1crypto",
"jinja2",
- "Click"
+ "Click",
+ "rainbow @ git+https://github.com/Ledger-Donjon/rainbow@master"
],
extras_require={
"dev": ["mypy", "flake8"],
diff --git a/test/test_impl.py b/test/test_impl.py
index ea92f8a..b054567 100644
--- a/test/test_impl.py
+++ b/test/test_impl.py
@@ -255,4 +255,4 @@ class ECDSATests(ImplTests):
self.do_ecdsa_test(runner, self.secp128r1, LTRMultiplier, ["add-1998-cmo", "dbl-1998-cmo"],
"ltr", complete=False, always=True)
self.do_ecdsa_test(runner, self.secp128r1, LTRMultiplier, ["add-1998-cmo", "dbl-1998-cmo"],
- "ltr", complete=True, always=True)
+ "ltr", complete=True, always=True) \ No newline at end of file
diff --git a/test/test_simulator.py b/test/test_simulator.py
new file mode 100644
index 0000000..d741cf4
--- /dev/null
+++ b/test/test_simulator.py
@@ -0,0 +1,194 @@
+from copy import copy
+from os.path import join
+from unittest import TestCase
+
+from click.testing import CliRunner
+from pyecsca.ec.key_agreement import ECDH_SHA1
+from pyecsca.ec.mult import LTRMultiplier, RTLMultiplier, CoronMultiplier, BinaryNAFMultiplier
+from pyecsca.ec.signature import ECDSA_SHA1, SignatureResult
+
+from pyecsca.codegen.builder import build_impl
+from pyecsca.codegen.client import SimulatorTarget
+
+from pyecsca.ec.curve import EllipticCurve
+from pyecsca.ec.mod import Mod
+from pyecsca.ec.model import ShortWeierstrassModel
+from pyecsca.ec.params import DomainParameters
+from pyecsca.ec.point import InfinityPoint, Point
+import gc
+
+
+class SimulatorTest(TestCase):
+
+ def setUp(self):
+ model = ShortWeierstrassModel()
+ coords = model.coordinates["projective"]
+ p = 0xd7d1247f
+ a = Mod(0xa4a44016, p)
+ b = Mod(0x73f76716, p)
+ n = 0xd7d2a475
+ h = 1
+ gx, gy, gz = Mod(0x54eed6d7, p), Mod(0x6f1e55ac, p), Mod(1, p)
+ generator = Point(coords, X=gx, Y=gy, Z=gz)
+ neutral = InfinityPoint(coords)
+
+ curve = EllipticCurve(model, coords, p, neutral, {"a": a, "b": b})
+ self.curve32 = DomainParameters(curve, generator, n, h)
+ self.base = self.curve32.generator
+ self.coords = self.curve32.curve.coordinate_model
+
+ def do_basic_test(self, callback, runner, params, mult_class, formulas, mult_name,
+ ecdsa, ecdh):
+ with runner.isolated_filesystem() as tmpdir:
+ runner.invoke(build_impl,
+ ["--platform", "STM32F3",
+ "--ecdsa" if ecdsa else "--no-ecdsa",
+ "--ecdh" if ecdh else "--no-ecdh",
+ params.curve.model.shortname, params.curve.coordinate_model.name,
+ *formulas,
+ f"{mult_name}()",
+ "."])
+ target = SimulatorTarget(params.curve.model, params.curve.coordinate_model)
+ target.connect(binary=join(tmpdir, "pyecsca-codegen-CW308_STM32F3.elf"))
+ target.set_params(params)
+ formula_instances = [params.curve.coordinate_model.formulas[formula] for formula
+ in formulas]
+ mult = mult_class(*formula_instances)
+ mult.init(params, params.generator)
+ callback(target, mult, params)
+ target.disconnect()
+ del target
+ gc.collect()
+
+
+class PRNGTests(SimulatorTest):
+
+ def test_init(self):
+ runner = CliRunner()
+
+ def callback(target, mult, params):
+ target.init_prng(bytes([0x12, 0x34, 0x56, 0x78]))
+
+ self.do_basic_test(callback, runner, self.curve32, LTRMultiplier,
+ ["add-1998-cmo", "dbl-1998-cmo"], "ltr", False, False)
+
+
+class SetupTests(SimulatorTest):
+
+ def test_setup(self):
+ runner = CliRunner()
+
+ def callback(target, mult, params):
+ priv = 57
+ pub = mult.multiply(priv).to_affine()
+ target.set_privkey(priv)
+ target.set_pubkey(pub)
+
+ self.do_basic_test(callback, runner, self.curve32, LTRMultiplier,
+ ["add-1998-cmo", "dbl-1998-cmo"], "ltr", False, False)
+
+ def test_debug(self):
+ runner = CliRunner()
+
+ def callback(target, mult, params):
+ model, coords = target.debug()
+ self.assertEqual(model, params.curve.model.shortname)
+ self.assertEqual(coords, params.curve.coordinate_model.name)
+
+ self.do_basic_test(callback, runner, self.curve32, LTRMultiplier,
+ ["add-1998-cmo", "dbl-1998-cmo"], "ltr", False, False)
+
+
+class KeyGenerationTests(SimulatorTest):
+
+ def do_keygen_test(self, runner, params, mult_class, formulas, mult_name):
+ def callback(target, mult, params):
+ priv, pub = target.generate()
+ self.assertTrue(params.curve.is_on_curve(pub))
+ expected = mult.multiply(priv).to_affine()
+ self.assertEqual(pub, expected)
+
+ self.do_basic_test(callback, runner, params, mult_class, formulas, mult_name, False, False)
+
+ def test_ltr(self):
+ runner = CliRunner()
+ self.do_keygen_test(runner, self.curve32, LTRMultiplier, ["add-1998-cmo", "dbl-1998-cmo"],
+ "ltr")
+
+ def test_rtl(self):
+ runner = CliRunner()
+ self.do_keygen_test(runner, self.curve32, RTLMultiplier, ["add-1998-cmo", "dbl-1998-cmo"],
+ "rtl")
+
+
+class ScalarMultiplicationTests(SimulatorTest):
+
+ def do_mult_test(self, runner, params, mult_class, formulas, mult_name):
+ values = [2355498743]
+
+ def callback(target, mult, params):
+ for value in values:
+ result = target.scalar_mult(value, params.generator)
+ expected = mult.multiply(value)
+ self.assertEqual(result, expected)
+
+ self.do_basic_test(callback, runner, params, mult_class, formulas, mult_name, False, False)
+
+ def test_ltr(self):
+ runner = CliRunner()
+ self.do_mult_test(runner, self.curve32, LTRMultiplier, ["add-1998-cmo", "dbl-1998-cmo"],
+ "ltr")
+
+ def test_rtl(self):
+ runner = CliRunner()
+ self.do_mult_test(runner, self.curve32, RTLMultiplier, ["add-1998-cmo", "dbl-1998-cmo"],
+ "rtl")
+
+
+class ECDHTests(SimulatorTest):
+ def do_ecdh_test(self, runner, params, mult_class, formulas, mult_name):
+ other_privs = [2355498743]
+
+ def callback(target, mult, params):
+ for other_priv in other_privs:
+ priv, pub = target.generate()
+ other_pub = mult.multiply(other_priv)
+ ecdh = ECDH_SHA1(copy(mult), params, other_pub, priv)
+ result = target.ecdh(other_pub)
+ expected = ecdh.perform()
+ self.assertEqual(result, expected)
+
+ self.do_basic_test(callback, runner, params, mult_class, formulas, mult_name, False, True)
+
+ def test_ltr(self):
+ runner = CliRunner()
+ self.do_ecdh_test(runner, self.curve32, LTRMultiplier, ["add-1998-cmo", "dbl-1998-cmo"],
+ "ltr")
+
+ def test_rtl(self):
+ runner = CliRunner()
+ self.do_ecdh_test(runner, self.curve32, RTLMultiplier, ["add-1998-cmo", "dbl-1998-cmo"],
+ "rtl")
+
+
+class ECDSATests(SimulatorTest):
+ def do_ecdsa_test(self, runner, params, mult_class, formulas, mult_name):
+ data = b"something"
+
+ def callback(target, mult, params):
+ priv, pub = target.generate()
+ ecdsa = ECDSA_SHA1(copy(mult), params, mult.formulas["add"],
+ pub.to_model(params.curve.coordinate_model, params.curve), priv)
+
+ signature_data = target.ecdsa_sign(data)
+ result = SignatureResult.from_DER(bytes(signature_data))
+ self.assertTrue(ecdsa.verify_data(result, data))
+
+ expected = ecdsa.sign_data(data).to_DER()
+ self.assertTrue(target.ecdsa_verify(data, expected))
+
+ self.do_basic_test(callback, runner, params, mult_class, formulas, mult_name, True, False)
+
+ def test_ltr(self):
+ runner = CliRunner()
+ self.do_ecdsa_test(runner, self.curve32, LTRMultiplier, ["add-1998-cmo", "dbl-1998-cmo"], "ltr") \ No newline at end of file