*.pyc | |||||
elmo/* | |||||
**/__pycache__/** | |||||
build/* | |||||
python_elmo.egg-info/* | |||||
*.pyc | |||||
.DS_Store |
recursive-include elmo/projects * | |||||
recursive-include elmo/templates * | |||||
recursive-exclude elmo/elmo-tool * |
# Online ELMO | |||||
# Python ELMO | |||||
_Online ELMO_ is a Python library which proposes an encapsulation of the project _ELMO_. | |||||
_Python ELMO_ is a Python library which proposes an encapsulation of the project _ELMO_. | |||||
[MOW17] **Towards Practical Tools for Side | [MOW17] **Towards Practical Tools for Side | ||||
Channel Aware Software Engineering : ’Grey Box’ Modelling for Instruction Leakages** | Channel Aware Software Engineering : ’Grey Box’ Modelling for Instruction Leakages** | ||||
## Requirements | ## Requirements | ||||
To use _Online ELMO_, you need at least Python3.5 and the packages present in the file requirements.txt | |||||
To use _Python ELMO_, you need at least Python3.5 and ```numpy```. | |||||
```bash | |||||
pip3 install -r requirements.txt | |||||
``` | |||||
The library will install and compile ELMO. So, you need the GCC compiler collection and the command/utility 'make' (for more details, see the documentation of ELMO). On Ubuntu/Debian, | |||||
The library will install and compile ELMO. So, you need the GCC compiler collection and the command/utility 'make' (for more details, see the documentation of ELMO). On Ubuntu/Debian, | |||||
```bash | ```bash | ||||
sudo apt install build-essential | sudo apt install build-essential | ||||
## Installation | ## Installation | ||||
First, download _Online ELMO_. | |||||
First, download _Python ELMO_. | |||||
```bash | ```bash | ||||
git clone https://git.aprilas.fr/tfeneuil/OnlineELMO | |||||
git clone https://git.aprilas.fr/tfeneuil/python-elmo | |||||
``` | ``` | ||||
And then, install ELMO thanks to the script of installation. | And then, install ELMO thanks to the script of installation. | ||||
```bash | ```bash | ||||
./install | |||||
python setup.py install | |||||
``` | ``` | ||||
## Usage | ## Usage | ||||
To start a new project, you can use the following function. | To start a new project, you can use the following function. | ||||
```python | ```python | ||||
from elmo_online.manage import create_simulation | |||||
from elmo.manage import create_simulation | |||||
create_simulation( | create_simulation( | ||||
'dilithium', # The (relative) path of the project | 'dilithium', # The (relative) path of the project | ||||
'DilithiumSimulation' # The classname of the simulation | 'DilithiumSimulation' # The classname of the simulation | ||||
### List all the available simulation | ### List all the available simulation | ||||
```python | ```python | ||||
from elmo_online.manage import search_simulations | |||||
from elmo.manage import search_simulations | |||||
search_simulations('.') | search_simulations('.') | ||||
``` | ``` | ||||
'KyberNTTSimulation': <class 'KyberNTTSimulation'>} | 'KyberNTTSimulation': <class 'KyberNTTSimulation'>} | ||||
``` | ``` | ||||
_Online ELMO_ offers a example project to you in the repository _projects/Examples_ of the module. This example is a project to generate traces of the execution of the NTT implemented in the cryptosystem [Kyber](https://pq-crystals.org/kyber/). | |||||
_Python ELMO_ offers a example project to you in the repository _projects/Examples_ of the module. This example is a project to generate traces of the execution of the NTT implemented in the cryptosystem [Kyber](https://pq-crystals.org/kyber/). | |||||
### Use a simulation project | ### Use a simulation project | ||||
Warning! Before using it, you have to compile your project thanks to the provided Makefile. | Warning! Before using it, you have to compile your project thanks to the provided Makefile. | ||||
```python | ```python | ||||
from elmo_online.manage import get_simulation | |||||
from elmo.manage import get_simulation | |||||
KyberNTTSimulation = get_simulation_via_classname('KyberNTTSimulation') | KyberNTTSimulation = get_simulation_via_classname('KyberNTTSimulation') | ||||
import numpy as np | import numpy as np | ||||
# And now, I can draw and analyse the traces | # And now, I can draw and analyse the traces | ||||
``` | ``` | ||||
### Use a simulution project thanks to a server | |||||
### Use a simulation project thanks to a server | |||||
Sometimes, it is impossible to run the simulation thanks the simple method _run_ of the project class. Indeed, sometimes the Python script is executed in the environment where _Online ELMO_ cannot launch the ELMO tool. For example, it is the case where _Online ELMO_ is used in SageMath on Windows. On Windows, SageMath installation relies on the Cygwin POSIX emulation system and it can be a problem. | |||||
Sometimes, it is impossible to run the simulation thanks the simple method _run_ of the project class. Indeed, sometimes the Python script is executed in the environment where _Python ELMO_ cannot launch the ELMO tool. For example, it is the case where _Python ELMO_ is used in SageMath on Windows. On Windows, SageMath installation relies on the Cygwin POSIX emulation system and it can be a problem. | |||||
To offer a solution, _Online ELMO_ can be used thanks to a link client-server. The idea is you must launch the script _run_server.py_ which will listen (by default) at port 5000 in localhost. | |||||
To offer a solution, _Online ELMO_ can be used thanks to a link client-server. The idea is you must launch the script _run\_server.py_ which will listen (by default) at port 5000 in localhost. | |||||
```bash | ```bash | ||||
python3 run_server.py | |||||
python -m elmo run-server | |||||
``` | ``` | ||||
And after, you can manipulate the projects as described in the previous section by replacing _run_ to _run_online_. | |||||
And after, you can manipulate the projects as described in the previous section by replacing _run_ to _run\_online_. | |||||
```python | ```python | ||||
from elmo_online.manage import get_simulation | |||||
KyberNTTSimulation = get_simulation_via_classname('KyberNTTSimulation') | |||||
from elmo.manage import get_simulation | |||||
KyberNTTSimulation = get_simulation('KyberNTTSimulation') | |||||
import numpy as np | import numpy as np | ||||
Kyber512 = {'k': 2, 'n': 256} | Kyber512 = {'k': 2, 'n': 256} | ||||
# And now, I can draw and analyse the traces | # And now, I can draw and analyse the traces | ||||
``` | ``` | ||||
Warning! Using the _run_online_ method doesn't exempt you from compiling the project with the provided Makefile. | |||||
Warning! Using the _run\_online_ method doesn't exempt you from compiling the project with the provided Makefile. | |||||
### Use the ELMO Engine | ### Use the ELMO Engine | ||||
- "_**OTHER**_" for the other instructions. | - "_**OTHER**_" for the other instructions. | ||||
```python | ```python | ||||
from elmo_online.engine import ELMOEngine, Instr | |||||
from elmo.engine import ELMOEngine, Instr | |||||
engine = ELMOEngine() | engine = ELMOEngine() | ||||
for i in range(0, 256): | for i in range(0, 256): | ||||
engine.add_point( | engine.add_point( |
import os, shutil | |||||
import re | |||||
from .manage import create_simulation | |||||
print('Creation of a new simulation project...') | |||||
### Create the repository of the projects | |||||
global_path = 'projects' | |||||
os.makedirs(global_path, exist_ok=True) | |||||
### Get the project classname | |||||
project_classname = '' | |||||
search = re.compile(r'[^a-zA-Z0-9_]').search | |||||
while not project_classname: | |||||
classname = input(' - What is the project classname? ') | |||||
if search(classname): | |||||
print(' > Illegal characters detected! Please enter a name with only the following characters : a-z, A-Z, 0-9, and "_".') | |||||
else: | |||||
project_classname = classname.strip() | |||||
### Get and create the project repository | |||||
search = re.compile(r'[^a-zA-Z0-9.-_/]').search | |||||
project_repository = '' | |||||
while not project_repository: | |||||
repository = input(' - What is the project repository? ') | |||||
if search(repository): | |||||
print('Illegal characters detected! Please enter a name with only the following characters : a-z, A-Z, 0-9, ".", "-", "_" and "/".') | |||||
else: | |||||
project_repository = repository | |||||
project_path = global_path+'/'+repository | |||||
project_path = create_simulation(project_path, classname) | |||||
print('') | |||||
print('Creation complete !') | |||||
print(' - Project repository: {}'.format(project_path)) | |||||
print(' - Project class "{}" in {}'.format(project_classname, project_path+'/projectclass.py')) | |||||
print(' - Linker script: {}'.format(project_path+'/project.ld'))) | |||||
print('') | |||||
print('Please don\'t to compile the project with the present Makefile before using it!') |
elmo-tool/* | |||||
*.pyc |
from .manage import * | |||||
from .engine import * |
import sys | |||||
from .utils import Color | |||||
if len(sys.argv) <= 1: | |||||
print(Color.FAIL + 'Please enter an instruction.' + Color.ENDC) | |||||
exit() | |||||
command = sys.argv[1] | |||||
if command == 'create-simulation': | |||||
import os, shutil | |||||
import re | |||||
print('Creation of a new simulation project...') | |||||
### Create the repository of the projects | |||||
global_path = 'projects' | |||||
os.makedirs(global_path, exist_ok=True) | |||||
### Get the project classname | |||||
project_classname = '' | |||||
search = re.compile(r'[^a-zA-Z0-9_]').search | |||||
while not project_classname: | |||||
classname = input(' - What is the project classname? ') | |||||
if search(classname): | |||||
print(' > Illegal characters detected! Please enter a name with only the following characters : a-z, A-Z, 0-9, and "_".') | |||||
else: | |||||
project_classname = classname.strip() | |||||
### Get and create the project repository | |||||
search = re.compile(r'[^a-zA-Z0-9.-_/]').search | |||||
project_repository = '' | |||||
while not project_repository: | |||||
repository = input(' - What is the project repository? ') | |||||
if search(repository): | |||||
print('Illegal characters detected! Please enter a name with only the following characters : a-z, A-Z, 0-9, ".", "-", "_" and "/".') | |||||
else: | |||||
project_repository = repository | |||||
project_path = global_path+'/'+repository | |||||
from .manage import create_simulation | |||||
project_path = create_simulation(project_path, classname) | |||||
print('') | |||||
print('Creation complete !') | |||||
print(' - Project repository: {}'.format(project_path)) | |||||
print(' - Project class "{}" in {}'.format(project_classname, project_path+'/projectclass.py')) | |||||
print(' - Linker script: {}'.format(project_path+'/project.ld')) | |||||
print('') | |||||
print('Please don\'t to compile the project with the present Makefile before using it!') | |||||
exit() | |||||
if command == 'run-server': | |||||
from .server.servicethread import ListeningThread | |||||
from .executorthread import ExecutorThread | |||||
def do_main_program(): | |||||
global thread, stop | |||||
thread = ListeningThread('localhost', 5000, ExecutorThread) | |||||
thread.start() | |||||
def program_cleanup(signum, frame): | |||||
global thread, stop | |||||
thread.stop() | |||||
stop = True | |||||
thread = None | |||||
stop = False | |||||
# Execute | |||||
print("Executing...") | |||||
do_main_program() | |||||
print("Done ! And now, listening...") | |||||
import signal | |||||
signal.signal(signal.SIGINT, program_cleanup) | |||||
signal.signal(signal.SIGTERM, program_cleanup) | |||||
# Wait | |||||
import time | |||||
while not stop: | |||||
time.sleep(1) | |||||
exit() | |||||
print(Color.FAIL + 'Unknown Command.' + Color.ENDC) | |||||
exit() |
import os | |||||
### Configuration of the module | |||||
TEMPLATE_REPOSITORY = 'templates' | |||||
PROJECTS_REPOSITORY = 'projects' | |||||
ELMO_TOOL_REPOSITORY = 'elmo-tool' | |||||
ELMO_EXECUTABLE_NAME = 'elmo' | |||||
ELMO_OUTPUT_ENCODING = 'latin-1' | |||||
ELMO_INPUT_FILE_NAME = 'input.txt' | |||||
MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) | |||||
SEARCH_EXCLUSION_TAG = 'EXCLUDE-FROM-SIMULATION-SEARCH' |
import os | import os | ||||
from enum import IntEnum, unique | from enum import IntEnum, unique | ||||
def hweight(n): | |||||
c = 0 | |||||
while n>0: | |||||
c += (n & 1) | |||||
n >>= 1 | |||||
return c | |||||
def hdistance(x,y): | |||||
return hweight(x^y) | |||||
def binary_writing(n, nb_bits=32, with_hamming=False): | |||||
n = np.array(n) | |||||
w, h = np.zeros((nb_bits, len(n))), np.zeros((len(n))) | |||||
for ind in range(nb_bits): | |||||
w[ind] = (n & 1) | |||||
h += w[ind] | |||||
n >>= 1 | |||||
ind += 1 | |||||
return (w, h) if with_hamming else w | |||||
from .utils import binary_writing | |||||
from .config import ELMO_TOOL_REPOSITORY | |||||
@unique | @unique | ||||
class Instr(IntEnum): | |||||
class Instruction(IntEnum): | |||||
EOR = 0 | EOR = 0 | ||||
LSL = 1 | LSL = 1 | ||||
STR = 2 | STR = 2 | ||||
LDR = 3 | LDR = 3 | ||||
MUL = 4 | MUL = 4 | ||||
OTHER = 5 | OTHER = 5 | ||||
# Short name for 'Instruction' class | |||||
Instr = Instruction | |||||
PREVIOUS = 0 | PREVIOUS = 0 | ||||
CURRENT = 1 | CURRENT = 1 | ||||
return coeffs | return coeffs | ||||
def load_coefficients(self): | def load_coefficients(self): | ||||
filename = os.path.dirname(os.path.abspath(__file__))+'/elmo/coeffs.txt' | |||||
filename = os.path.join( | |||||
os.path.dirname(os.path.abspath(__file__)), | |||||
ELMO_TOOL_REPOSITORY, | |||||
'coeffs.txt', | |||||
) | |||||
self.coefficients = None | self.coefficients = None | ||||
with open(filename, 'r') as _file: | with open(filename, 'r') as _file: | ||||
self.coefficients = np.array([list(map(float, line.split())) for line in _file.readlines()[:2153]]) | self.coefficients = np.array([list(map(float, line.split())) for line in _file.readlines()[:2153]]) |
import os, shutil | |||||
import re | |||||
import inspect | |||||
import subprocess | |||||
import sys | |||||
from os.path import join as pjoin | |||||
from .config import ( | |||||
TEMPLATE_REPOSITORY, | |||||
PROJECTS_REPOSITORY, | |||||
ELMO_TOOL_REPOSITORY, | |||||
ELMO_EXECUTABLE_NAME, | |||||
MODULE_PATH, | |||||
ELMO_OUTPUT_ENCODING, | |||||
SEARCH_EXCLUSION_TAG, | |||||
) | |||||
from .project_base import SimulationProject | |||||
from .utils import Color | |||||
############ GETTERS ############ | |||||
def search_simulations_in_repository(repository, criteria=lambda x:True): | |||||
""" Search simulation classes in the 'repository' verifying the 'criteria' | |||||
Return a list of 'SimulationProject' subclasses | |||||
:repository: Repository of the searched simulation classes (string) | |||||
:criteria: Boolean function with 'SimulationProject' subclasses for input | |||||
""" | |||||
projects = {} | |||||
from .utils import write | |||||
for root, repositories, files in os.walk(repository): | |||||
for filename in files: | |||||
if re.fullmatch(r'.*project.*\.py', filename): | |||||
complete_filename = pjoin(root, filename) | |||||
# Encapsulate the project | |||||
globals = { | |||||
#'__builtins__': {'__build_class__': __build_class__}, | |||||
'SimulationProject': SimulationProject, | |||||
'write': write, | |||||
} | |||||
locals = {} | |||||
# Read the project code | |||||
with open(complete_filename, 'r') as _file: | |||||
project = ''.join(_file.read()) | |||||
if ('SimulationProject' not in project) or (SEARCH_EXCLUSION_TAG in project): | |||||
continue # Exclude this file | |||||
exec(project, globals, locals) | |||||
# Extract the simulations | |||||
for key, obj in locals.items(): | |||||
if inspect.isclass(obj) and issubclass(obj, SimulationProject): | |||||
if criteria(obj): | |||||
if key in projects: | |||||
print(Color.WARNING + \ | |||||
'Warning! Many simulations with the same name. ' + \ | |||||
'Simulation ignored: {} in {}'.format( | |||||
key, complete_filename[len(repository)+1:]) + \ | |||||
Color.ENDC | |||||
) | |||||
else: | |||||
obj.set_project_directory(os.path.abspath(root)) | |||||
projects[key] = obj | |||||
return projects | |||||
def search_simulations_in_module(criteria=lambda x:True): | |||||
""" Search simulation classes among the module projects verifying the 'criteria' | |||||
Return a list of 'SimulationProject' subclasses | |||||
:criteria: Boolean function with 'SimulationProject' subclasses for input | |||||
""" | |||||
projects_path = pjoin(MODULE_PATH, PROJECTS_REPOSITORY) | |||||
return search_simulations_in_repository(projects_path, criteria) | |||||
def search_simulations(repository='.', criteria=lambda x:True): | |||||
""" Search simulation classes in the 'repository' and among | |||||
the module projects verifying the 'criteria' | |||||
Return a list of 'SimulationProject' subclasses | |||||
:repository: Repository of the searched simulation classes (string) | |||||
:criteria: Boolean function with 'SimulationProject' subclasses for input | |||||
""" | |||||
projects = search_simulations_in_repository(repository, criteria) | |||||
module_projects = search_simulations_in_module(criteria) | |||||
for key, project in module_projects.items(): | |||||
if key not in projects: | |||||
projects[key] = project | |||||
return projects | |||||
class SimulationNotFoundError(Exception): | |||||
pass | |||||
class TooManySimulationsError(Exception): | |||||
pass | |||||
def get_simulation(classname=None, repository='.'): | |||||
""" Get a simulation class in the 'repository' with the specific 'classname' | |||||
Return a subclass of 'SimulationProject' class | |||||
:classname: Name of the searched simulation class (string, optional) | |||||
:repository: Repository of the searched simulation class (string, optional) | |||||
""" | |||||
criteria = lambda x: True | |||||
if classname is not None: | |||||
criteria = lambda x: x.__name__ == classname.strip() | |||||
projects = search_simulations(repository, criteria) | |||||
if len(projects) == 1: | |||||
return list(projects.values())[0] | |||||
elif len(projects) < 1: | |||||
raise SimulationNotFoundError() | |||||
else: | |||||
raise TooManySimulationsError() | |||||
############ SETTERS ############ | |||||
def create_simulation(repository, classname, is_a_module_project=False): | |||||
""" Create a Simulation Project. | |||||
It builds a repository with the standard content for the project. | |||||
Return the absolute path of the created repository | |||||
:repository: Name of repository which will be created (string) | |||||
:classname: Name of the Python class to manage the project (string) | |||||
:is_a_module_project: If True and :repository: is a relative path, | |||||
create the project among the module projects. | |||||
""" | |||||
# Update the repository, if necessary | |||||
if is_a_module_project and (not os.path.isabs(repository)): | |||||
repository = pjoin( | |||||
MODULE_PATH, | |||||
PROJECTS_REPOSITORY, | |||||
repository, | |||||
) | |||||
# Build the project repository | |||||
try: | |||||
os.makedirs(repository, exist_ok=False) | |||||
except FileExistsError as err: | |||||
raise FileExistsError('Error, a project with this repository already exists!') from err | |||||
elmo_path = pjoin(MODULE_PATH, ELMO_TOOL_REPOSITORY) | |||||
template_path = pjoin(MODULE_PATH, TEMPLATE_REPOSITORY) | |||||
project_path = repository | |||||
# Add standard content in the project | |||||
files_from_ELMO = [ | |||||
pjoin('Examples', 'elmoasmfunctions.o'), | |||||
pjoin('Examples', 'elmoasmfunctions.s'), | |||||
pjoin('Examples', 'elmoasmfunctionsdef.h'), | |||||
pjoin('Examples', 'DPATraces', 'MBedAES', 'vector.o'), | |||||
] | |||||
files_from_templates = [ | |||||
'elmoasmfunctionsdef-extension.h', | |||||
'Makefile', | |||||
'project.c' | |||||
] | |||||
for filename in files_from_ELMO: | |||||
shutil.copy(pjoin(elmo_path, filename), project_path) | |||||
for filename in files_from_templates: | |||||
shutil.copy(pjoin(template_path, filename), project_path) | |||||
shutil.copy( | |||||
pjoin(elmo_path, 'Examples', 'DPATraces', 'MBedAES', 'MBedAES.ld'), | |||||
pjoin(project_path, 'project.ld') | |||||
) | |||||
# Create the project class | |||||
with open(pjoin(template_path, 'projectclass.txt')) as _source: | |||||
code = ''.join(_source.readlines()) | |||||
code = code.replace('{{PROJECTCLASSNAME}}', classname) | |||||
with open(pjoin(project_path, 'projectclass.py'), 'w') as _dest: | |||||
_dest.write(code) | |||||
# Return the path of the created repository | |||||
return os.path.abspath(project_path) | |||||
############ USAGE FUNCTIONS ############ | |||||
class DontFindBinaryError(Exception): | |||||
pass | |||||
def execute_simulation(project): | |||||
""" Execute a simulation of the power leakage using ELMO tool | |||||
Return the output and the errors of the execution of ELMO tool | |||||
:project: Subclass of 'SimulationProject' defining all the parameters of the simulation | |||||
""" | |||||
elmo_path = pjoin(MODULE_PATH, ELMO_TOOL_REPOSITORY) | |||||
# Some checking before executing | |||||
if not isinstance(project, SimulationProject): | |||||
raise TypeError('The project is not an instance of \'SimulationProject\' class.') | |||||
leaking_binary_path = pjoin(project.get_project_directory(), project.get_binary_path()) | |||||
if not os.path.isfile(leaking_binary_path): | |||||
raise BinaryNotFoundError('Binary not found. Did you compile your project?') | |||||
if not os.path.isfile(pjoin(elmo_path, ELMO_EXECUTABLE_NAME)): | |||||
raise Exception('Installation Error: the executable of the ELMO tool is not found.') | |||||
# Launch generation of the traces by launching ELMO | |||||
command = '{} "{}"'.format( | |||||
pjoin('.', ELMO_EXECUTABLE_NAME), | |||||
leaking_binary_path, | |||||
) | |||||
process = subprocess.Popen(command, shell=True, | |||||
cwd=elmo_path, executable='/bin/bash', | |||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE | |||||
) | |||||
# Follow the generation | |||||
output, error = b'', b'' | |||||
num_trace = 0 | |||||
while True: | |||||
output_line = process.stdout.readline() | |||||
error_line = process.stderr.readline() | |||||
if (not output_line) and (not error_line) and (process.poll() is not None): | |||||
break | |||||
if error_line: | |||||
error += error_line | |||||
if output_line: | |||||
output += output_line | |||||
if 'TRACE NO' in output_line.decode(ELMO_OUTPUT_ENCODING): | |||||
num_trace += 1 | |||||
return_code = process.poll() | |||||
# Treat data | |||||
output = output.decode(ELMO_OUTPUT_ENCODING) if output else None | |||||
error = error.decode(ELMO_OUTPUT_ENCODING) if error else None | |||||
nb_traces = None | |||||
nb_instructions = None | |||||
if output: | |||||
nb_traces = output.count('TRACE NO') | |||||
nb_instructions_pattern = re.compile(r'\s*instructions/cy..es\s+(\d+)\s*') | |||||
for line in output.split('\n'): | |||||
res = nb_instructions_pattern.fullmatch(line) | |||||
if res: | |||||
nb_instructions = res.group(1) | |||||
# Return results | |||||
return { | |||||
'nb_traces': nb_traces, | |||||
'nb_instructions': nb_instructions, | |||||
'output': output, | |||||
'error': error, | |||||
} | |||||
import os, re | import os, re | ||||
import socket | |||||
from .protocol import SocketTool | |||||
import numpy as np | import numpy as np | ||||
def to_hex(v, nb_bits=16): | |||||
try: | |||||
v_hex = v.hex() | |||||
except AttributeError: | |||||
v_hex = hex(v)[2:] | |||||
return '0'*(nb_bits//4-len(v_hex)) + v_hex | |||||
def split_octet(hexstr): | |||||
return [hexstr[i:i+2] for i in range(0, len(hexstr), 2)] | |||||
def to_signed_hex(v, nb_bits=16): | |||||
try: | |||||
return split_octet(to_hex(v & (2**nb_bits-1), nb_bits=nb_bits)) | |||||
except TypeError as err: | |||||
raise TypeError('Error to transform a <{}> into signed hex.'.format(type(v))) from err | |||||
def write(_input, uintXX, nb_bits=16): | |||||
uintXX = to_signed_hex(uintXX, nb_bits=nb_bits) | |||||
for i in range(nb_bits//8): | |||||
_input.write(uintXX[i]+'\n') | |||||
def write_list(_input, uint16_list): | |||||
for uint16 in uint16_list: | |||||
write(_input, uint16) | |||||
from .config import MODULE_PATH, ELMO_TOOL_REPOSITORY | |||||
from .utils import write | |||||
class SimulationProject: | class SimulationProject: | ||||
# TAG: EXCLUDE-FROM-SIMULATION-SEARCH | |||||
""" Class to manage a simultion | |||||
It contains all the parameters of the simulation and has method to use it | |||||
""" | |||||
_nb_bits_for_nb_challenges = 16 | _nb_bits_for_nb_challenges = 16 | ||||
_project_directory = None | _project_directory = None | ||||
### Define the project | ### Define the project | ||||
@classmethod | @classmethod | ||||
def get_project_directory(cl): | def get_project_directory(cl): | ||||
""" """ | |||||
if cl._project_directory: | if cl._project_directory: | ||||
return cl._project_directory | return cl._project_directory | ||||
else: | else: | ||||
return '' | return '' | ||||
@classmethod | @classmethod | ||||
def get_binary(cl): | |||||
def get_binary_path(cl): | |||||
raise NotImplementedError() | raise NotImplementedError() | ||||
@classmethod | @classmethod | ||||
def get_parameters_names(cl): | def get_parameters_names(cl): | ||||
return set() | return set() | ||||
@classmethod | |||||
def adapt_project(cl, parameters): | |||||
return | |||||
def get_challenge_format(self): | def get_challenge_format(self): | ||||
raise NotImplementedError() | raise NotImplementedError() | ||||
### Tools to realize the simulation of the project | ### Tools to realize the simulation of the project | ||||
def __init__(self, challenges=None): | def __init__(self, challenges=None): | ||||
self.elmo_folder = os.path.dirname(os.path.abspath(__file__))+'/elmo' | |||||
self.elmo_folder = os.path.join(MODULE_PATH, ELMO_TOOL_REPOSITORY) | |||||
self.challenges = challenges | self.challenges = challenges | ||||
self.reset() | self.reset() | ||||
self._complete_results = None | self._complete_results = None | ||||
self._complete_printed_data = None | self._complete_printed_data = None | ||||
def get_test_challenges(self): | |||||
raise NotImplementedError() | |||||
def get_random_challenges(self, nb_challenges): | |||||
raise NotImplementedError() | |||||
def set_challenges(self, challenges): | def set_challenges(self, challenges): | ||||
self.challenges = challenges | self.challenges = challenges | ||||
aux(format[num_part], challenge[num_part]) | aux(format[num_part], challenge[num_part]) | ||||
def set_input(self, input): | def set_input(self, input): | ||||
assert len(self.challenges) < 2**16, 'The number of challenges must be strictly lower than 65536. Currently, there are {} challenges.'.format(len(self.challenges)) | |||||
write(input, len(self.challenges), nb_bits=self._nb_bits_for_nb_challenges) | |||||
for challenge in self.challenges: | |||||
self.set_input_for_each_challenge(input, challenge) | |||||
if self.challenges: | |||||
assert len(self.challenges) < (1 << self._nb_bits_for_nb_challenges), \ | |||||
'The number of challenges must be strictly lower than {}. Currently, there are {} challenges.'.format( | |||||
1 << self._nb_bits_for_nb_challenges, | |||||
len(self.challenges), | |||||
) | |||||
write(input, len(self.challenges), nb_bits=self._nb_bits_for_nb_challenges) | |||||
for challenge in self.challenges: | |||||
self.set_input_for_each_challenge(input, challenge) | |||||
def run(self): | def run(self): | ||||
self.reset() | self.reset() | ||||
return res | return res | ||||
def run_online(self, host='localhost', port=5000): | def run_online(self, host='localhost', port=5000): | ||||
from .server.protocol import SocketTool | |||||
import socket | |||||
class TempInput: | class TempInput: | ||||
def __init__(self): | def __init__(self): | ||||
self._buffer = '' | self._buffer = '' |
*.elf | *.elf | ||||
*.list | *.list | ||||
*.bin | |||||
*.map | *.map | ||||
*.d | *.d | ||||
*.o | *.o |
### - get_simulation_via_classname(classname) | ### - get_simulation_via_classname(classname) | ||||
class KyberNTTSimulation(SimulationProject): | class KyberNTTSimulation(SimulationProject): | ||||
KYBER_K = 2 #k=2 for Kyber512 | |||||
KYBER_N = 256 #n=256 for Kyber512 | |||||
@classmethod | @classmethod | ||||
def get_binary(cl): | |||||
def get_binary_path(cl): | |||||
return 'project.bin' | return 'project.bin' | ||||
def __init__(self, *args, **kwargs): | def __init__(self, *args, **kwargs): | ||||
secret = challenge | secret = challenge | ||||
# Write the secret vector | # Write the secret vector | ||||
for j in range(2): #k=2 for Kyber512 | |||||
for k in range(256): #n=256 for Kyber512 | |||||
write(input, secret[j,k]) | |||||
for j in range(self.KYBER_K): | |||||
for k in range(self.KYBER_N): | |||||
write(input, secret[j,k]) | |||||
def get_test_challenges(self): | |||||
import numpy as np | |||||
just_ones = np.ones((self.KYBER_K, self.KYBER_N), dtype=int) | |||||
return [ | |||||
0 * just_ones, | |||||
1 * just_ones, | |||||
-2 * just_ones, | |||||
] | |||||
def get_random_challenges(self, nb_challenges=5): | |||||
import numpy as np | |||||
return [ np.random.choice( | |||||
[-2, -1, 0, 1, 2], | |||||
(self.KYBER_K, self.KYBER_N), | |||||
p=[1/16, 4/16, 6/16, 4/16, 1/16], | |||||
) for _ in range(nb_challenges) ] |
from servicethread import OneShotServiceThread | |||||
from .server.servicethread import OneShotServiceThread | |||||
import subprocess | import subprocess | ||||
import shutil | import shutil |
import threading | import threading | ||||
from protocol import Protocol, ClosureException | |||||
from .server.protocol import Protocol, ClosureException | |||||
import socket | import socket | ||||
### to write an integer of 'nb_bits' bits in the 'input_file'. | ### to write an integer of 'nb_bits' bits in the 'input_file'. | ||||
### To get this simulation class in Python scripts, please use the functions in manage.py as | ### To get this simulation class in Python scripts, please use the functions in manage.py as | ||||
### - search_simulations(repository) | ### - search_simulations(repository) | ||||
### - get_simulation(repository, classname=None) | |||||
### - get_simulation_via_classname(classname) | |||||
### - get_simulation(repository='.', classname=None) | |||||
class {{PROJECTCLASSNAME}}(SimulationProject): | class {{PROJECTCLASSNAME}}(SimulationProject): | ||||
@classmethod | @classmethod | ||||
def get_binary(cl): | |||||
def get_binary_path(cl): | |||||
return 'project.bin' | return 'project.bin' | ||||
def __init__(self, *args, **kwargs): | def __init__(self, *args, **kwargs): |
import numpy as np | |||||
### Binary operations | |||||
def hweight(n): | |||||
c = 0 | |||||
while n>0: | |||||
c += (n & 1) | |||||
n >>= 1 | |||||
return c | |||||
def hdistance(x,y): | |||||
return hweight(x^y) | |||||
def binary_writing(n, nb_bits=32, with_hamming=False): | |||||
n = np.array(n) | |||||
w, h = np.zeros((nb_bits, len(n))), np.zeros((len(n))) | |||||
for ind in range(nb_bits): | |||||
w[ind] = (n & 1) | |||||
h += w[ind] | |||||
n >>= 1 | |||||
ind += 1 | |||||
return (w, h) if with_hamming else w | |||||
### Conversion | |||||
def to_hex(v, nb_bits=16): | |||||
try: | |||||
v_hex = v.hex() | |||||
except AttributeError: | |||||
v_hex = hex(v)[2:] | |||||
return '0'*(nb_bits//4-len(v_hex)) + v_hex | |||||
def split_octet(hexstr): | |||||
return [hexstr[i:i+2] for i in range(0, len(hexstr), 2)] | |||||
def to_signed_hex(v, nb_bits=16): | |||||
try: | |||||
return split_octet(to_hex(v & ((1<<nb_bits)-1), nb_bits=nb_bits)) | |||||
except TypeError as err: | |||||
raise TypeError('Error to transform a <{}> into signed hex.'.format(type(v))) from err | |||||
### Write function | |||||
def write(_input, uint, nb_bits=16): | |||||
uint = to_signed_hex(uint, nb_bits=nb_bits) | |||||
for i in range(nb_bits//8): | |||||
_input.write(uint[i]+'\n') | |||||
def write_list(_input, uint_list, nb_bits=16): | |||||
for uint in uint_list: | |||||
write(_input, uint, nb_bits=nb_bits) | |||||
### Print Utils | |||||
class Color: | |||||
HEADER = '\033[95m' | |||||
OKBLUE = '\033[94m' | |||||
OKCYAN = '\033[96m' | |||||
OKGREEN = '\033[92m' | |||||
WARNING = '\033[93m' | |||||
FAIL = '\033[91m' | |||||
ENDC = '\033[0m' | |||||
BOLD = '\033[1m' | |||||
UNDERLINE = '\033[4m' |
#!/bin/bash | |||||
elmo_repository="elmo" | |||||
elmo_source="https://github.com/sca-research/ELMO.git" | |||||
if [ -d ${elmo_repository} ]; then | |||||
echo "ELMO tool already installed." | |||||
else | |||||
echo "ELMO tool NOT installed... Installing via online resources..." | |||||
# Download the tool | |||||
git clone --depth=1 --branch=master ${elmo_source} ${elmo_repository} | |||||
rm -rf ./${elmo_repository}/.git | |||||
# Compile the tool | |||||
cd ./${elmo_repository} | |||||
make | |||||
cd .. | |||||
fi |
import os, shutil | |||||
import re | |||||
import inspect | |||||
import subprocess | |||||
def search_simulations_in_repository(repository, criteria=lambda x:True): | |||||
""" To search simulation classes in the 'repository' verifying the 'criteria' """ | |||||
projects = {} | |||||
from .project_base import SimulationProject, write | |||||
for root, repositories, files in os.walk(repository): | |||||
for filename in files: | |||||
if re.fullmatch(r'.*project.*\.py', filename): | |||||
# Encapsulation the project | |||||
complete_filename = root+'/'+filename | |||||
globals = { | |||||
#'__builtins__': {'__build_class__': __build_class__}, | |||||
'SimulationProject': SimulationProject, | |||||
'write': write, | |||||
} | |||||
locals = {} | |||||
# Read the project code | |||||
with open(complete_filename, 'r') as _file: | |||||
project = '\n'.join(_file.readlines()) | |||||
exec(project, globals, locals) | |||||
# Extract the simulations | |||||
for key, obj in locals.items(): | |||||
if inspect.isclass(obj) and issubclass(obj, SimulationProject): | |||||
if criteria(obj): | |||||
if key in projects: | |||||
print('Warning! Multiplie simulation with the same name. Simulation ignored: {} in {}'.format(key, complete_filename[len(repository)+1:])) | |||||
else: | |||||
obj.set_project_directory(os.path.abspath(root)) | |||||
projects[key] = obj | |||||
return projects | |||||
def search_simulations_in_module(criteria=lambda x:True): | |||||
module_path = os.path.dirname(os.path.abspath(__file__)) | |||||
projects_path = module_path+'/projects' | |||||
return search_simulations_in_repository(projects_path, criteria) | |||||
def search_simulations(repository, criteria=lambda x:True): | |||||
projects = search_simulations_in_repository(repository, criteria) | |||||
module_projects = search_simulations_in_module(criteria) | |||||
for key, project in module_projects.items(): | |||||
if key not in projects: | |||||
projects[key] = project | |||||
return projects | |||||
class SimulationNotFoundError(Exception): | |||||
pass | |||||
class TooManySimulationsError(Exception): | |||||
pass | |||||
def get_simulation(repository, classname=None): | |||||
""" Get a simulation class in the 'repository' with the specific 'classname' """ | |||||
criteria = lambda x: True | |||||
if classname is not None: | |||||
criteria = lambda x: x.__name__ == classname.strip() | |||||
projects = search_simulations(repository, criteria) | |||||
if len(projects) == 1: | |||||
return list(projects.values())[0] | |||||
elif len(projects) < 1: | |||||
raise SimulationNotFoundError() | |||||
else: | |||||
raise TooManySimulationsError() | |||||
def get_simulation_via_classname(classname): | |||||
return get_simulation('.', classname) | |||||
def create_simulation(repository, classname): | |||||
""" Create a simulation class """ | |||||
try: | |||||
os.makedirs(repository, exist_ok=False) | |||||
except FileExistsError as err: | |||||
raise FileExistsError('Error, a project with this repository already exists!') from err | |||||
module_path = os.path.dirname(os.path.abspath(__file__)) | |||||
elmo_path = module_path+'/elmo' | |||||
template_path = module_path+'/templates' | |||||
project_path = repository | |||||
### Add contents in the project | |||||
files_from_ELMO = [ | |||||
'Examples/elmoasmfunctions.o', | |||||
'Examples/elmoasmfunctions.s', | |||||
'Examples/elmoasmfunctionsdef.h', | |||||
'Examples/DPATraces/MBedAES/vector.o', | |||||
] | |||||
files_from_templates = [ | |||||
'elmoasmfunctionsdef-extension.h', | |||||
'Makefile', | |||||
'project.c' | |||||
] | |||||
for filename in files_from_ELMO: | |||||
shutil.copy(elmo_path+'/'+filename, project_path) | |||||
for filename in files_from_templates: | |||||
shutil.copy(template_path+'/'+filename, project_path) | |||||
shutil.copy(elmo_path+'/'+'Examples/DPATraces/MBedAES/MBedAES.ld', project_path+'/'+'project.ld') | |||||
### Create the project class | |||||
with open(template_path+'/projectclass.py') as _source: | |||||
code = ''.join(_source.readlines()) | |||||
code = code.replace('{{PROJECTCLASSNAME}}', classname) | |||||
with open(project_path+'/'+'projectclass.py', 'w') as _dest: | |||||
_dest.write(code) | |||||
return os.path.abspath(project_path) | |||||
def execute_simulation(project, data=None): | |||||
""" Execute a simulation with 'data' """ | |||||
# Make the compilation | |||||
if False: | |||||
print('Compiling binary...') | |||||
# Adapt the project source | |||||
with open('binaries/Frodo/frodo-base.c', 'r') as _src_file: | |||||
content = _src_file.read() | |||||
with open('binaries/Frodo/frodo.c', 'w') as _dst_file: | |||||
_dst_file.write(content.replace('%NEED_TO_FILL%', str(n))) | |||||
# Compile the project | |||||
make_directory = 'projects/{}/{}'.format(project.get_project_directory(), project.get_make_directory()) | |||||
process = subprocess.Popen('make', shell=True, cwd=make_directory, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |||||
output, error = process.communicate() | |||||
if error and ('error' in error.decode('latin-1')): | |||||
print("Error to compile") | |||||
print(error) | |||||
raise Exception() | |||||
# Save last compilation data | |||||
global_variables[project_name] = { | |||||
'last_n': n, | |||||
} | |||||
# Generate the trace by launching ELMO | |||||
command = './elmo "{}/{}"'.format( | |||||
project.get_project_directory(), | |||||
project.get_binary() | |||||
) | |||||
cwd = os.path.dirname(os.path.abspath(__file__))+'/elmo' | |||||
process = subprocess.Popen(command, shell=True, cwd=cwd, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |||||
output, error = process.communicate() | |||||
output = output.decode('latin-1') if output else None | |||||
error = error.decode('latin-1') if error else None | |||||
nb_traces = output.count('TRACE NO') | |||||
# Return results | |||||
return { | |||||
'output': output, | |||||
'error': error, | |||||
'nb_traces': nb_traces, | |||||
} | |||||
numpy==1.19.1 |
from servicethread import ListeningThread | |||||
from executorthread import ExecutorThread | |||||
def do_main_program(): | |||||
global thread, stop | |||||
thread = ListeningThread('localhost', 5000, ExecutorThread) | |||||
thread.start() | |||||
def program_cleanup(signum, frame): | |||||
global thread, stop | |||||
thread.stop() | |||||
stop = True | |||||
thread = None | |||||
stop = False | |||||
# Execute | |||||
print("Executing...") | |||||
do_main_program() | |||||
print("Done ! And now, listening...") | |||||
import signal | |||||
signal.signal(signal.SIGINT, program_cleanup) | |||||
signal.signal(signal.SIGTERM, program_cleanup) | |||||
# Wait | |||||
import time | |||||
while not stop: | |||||
time.sleep(1) |
import setuptools | |||||
from setuptools.command.build_py import build_py | |||||
from setuptools.command.install import install | |||||
from subprocess import check_call | |||||
import os, shutil | |||||
import re | |||||
### CONFIGURATION | |||||
PELMO_SOURCE = 'https://github.com/ThFeneuil/python-elmo' | |||||
ELMO_MODULE_NAME = 'elmo' | |||||
ELMO_SOURCE = 'https://github.com/sca-research/ELMO.git' | |||||
# Import configuration of ELMO_MODULE, | |||||
# cannot use 'import elmo' because it can be ambiguous which module it will call | |||||
with open(os.path.join(ELMO_MODULE_NAME, 'config.py')) as _file: | |||||
globals = {'__file__': os.path.join( | |||||
os.path.abspath(ELMO_MODULE_NAME), | |||||
'config.py' | |||||
)} | |||||
exec(_file.read(), globals) | |||||
ELMO_TOOL_REPOSITORY = globals['ELMO_TOOL_REPOSITORY'] | |||||
ELMO_INPUT_FILE_NAME = globals['ELMO_INPUT_FILE_NAME'] | |||||
def install_elmo_tool(elmo_complete_path): | |||||
## Download the tool | |||||
elmo_download_command = "git clone --depth=1 --branch=master {url} {elmo_path}".format( | |||||
url=ELMO_SOURCE, | |||||
elmo_path=elmo_complete_path, | |||||
) | |||||
check_call(elmo_download_command.split()) | |||||
shutil.rmtree(os.path.join(elmo_complete_path, '.git')) | |||||
# 'test' contains a Python2 test, and it raises an error during byte-compiling | |||||
shutil.rmtree(os.path.join(elmo_complete_path, 'test')) | |||||
## Setup the tool | |||||
elmodefines_h = None | |||||
elmodefines_h_path = os.path.join(elmo_complete_path, 'elmodefines.h') | |||||
with open(elmodefines_h_path, 'r') as _file: | |||||
elmodefines_lines = _file.readlines() | |||||
for i, line in enumerate(elmodefines_lines): | |||||
if re.match(r'\s*#define\s+DATAFILEPATH', line): | |||||
elmodefines_lines[i] = '#define DATAFILEPATH "{}"'.format(ELMO_INPUT_FILE_NAME) | |||||
elmodefines_h = ''.join(elmodefines_lines) | |||||
with open(elmodefines_h_path, 'w') as _file: | |||||
_file.write(elmodefines_h) | |||||
# Compile the tool | |||||
check_call("make clean".split(), cwd=elmo_complete_path) | |||||
check_call("make".split(), cwd=elmo_complete_path) | |||||
class PostBuildCommand(build_py): | |||||
""" Build Command to add the ELMO installation """ | |||||
def run(self): | |||||
build_py.run(self) | |||||
# ELMO Installation | |||||
elmo_complete_path = os.path.join( | |||||
self.build_lib, | |||||
ELMO_MODULE_NAME, | |||||
ELMO_TOOL_REPOSITORY, | |||||
) | |||||
shutil.rmtree(elmo_complete_path, ignore_errors=True) | |||||
install_elmo_tool(elmo_complete_path) | |||||
if __name__ == '__main__': | |||||
with open("README.md", "r") as fh: | |||||
long_description = fh.read() | |||||
setuptools.setup( | |||||
name="python-elmo", | |||||
version="0.0.1", | |||||
author="Thibauld Feneuil", | |||||
author_email="thibauld.feneuil@cryptoexperts.com", | |||||
description="Emulator for power Leakage for the M0", | |||||
long_description=long_description, | |||||
long_description_content_type="text/markdown", | |||||
url=PELMO_SOURCE, | |||||
project_urls={ | |||||
'ELMO Source': ELMO_SOURCE, | |||||
'pELMO Source': PELMO_SOURCE, | |||||
}, | |||||
packages=setuptools.find_packages(), | |||||
classifiers=[ | |||||
"Programming Language :: Python :: 3", | |||||
], | |||||
python_requires='>=3.5', | |||||
cmdclass={ | |||||
'build_py': PostBuildCommand, | |||||
}, | |||||
install_requires=['numpy'], | |||||
include_package_data=True, | |||||
) |
import os, shutil | |||||
from subprocess import check_call | |||||
### Install ELMO | |||||
from elmo.config import ELMO_TOOL_REPOSITORY | |||||
ELMO_SOURCE = 'https://github.com/sca-research/ELMO.git' | |||||
elmo_complete_path = os.path.join('elmo', ELMO_TOOL_REPOSITORY) | |||||
if not os.path.isdir(elmo_complete_path): | |||||
from setup import install_elmo_tool | |||||
install_elmo_tool(elmo_complete_path) | |||||
def print_success(text): | |||||
OKGREEN = '\033[92m' | |||||
ENDC = '\033[0m' | |||||
print(OKGREEN+text+ENDC) | |||||
######################################################### | |||||
# TEST 1 : MANAGE A FRESH SIMULATION # | |||||
######################################################### | |||||
foldername = 'test-project' | |||||
classname = 'TestSimu' | |||||
### Create Simulation | |||||
from elmo.manage import create_simulation | |||||
shutil.rmtree('test-project', ignore_errors=True) # Clean before | |||||
create_simulation(foldername, classname) | |||||
assert os.path.isdir(foldername), 'Folder not created' | |||||
### List Available Simulations | |||||
from elmo.manage import search_simulations | |||||
simulations = search_simulations(foldername) | |||||
assert classname in simulations, 'Test Simulation not found' | |||||
### Use a Simulation | |||||
from elmo.manage import get_simulation | |||||
simu = get_simulation(classname, foldername) | |||||
shutil.rmtree(foldername) | |||||
print_success(' - Test 1 "Manage A Fresh Simulation": Success!') | |||||
######################################################### | |||||
# TEST 2 : USE ELMO ENGINE # | |||||
######################################################### | |||||
### Use the ELMO Engine | |||||
from elmo.engine import ELMOEngine, Instr | |||||
engine = ELMOEngine() | |||||
for i in range(0, 256): | |||||
engine.add_point( | |||||
(Instr.LDR, Instr.MUL, Instr.OTHER), # Types of the previous, current and next instructions | |||||
(0x0000, i), # Operands of the previous instructions | |||||
(0x2BAC, i) # Operands of the current instructions | |||||
) | |||||
engine.run() # Compute the power consumption of all these points | |||||
power = engine.power # Numpy 1D array with an entry for each previous point | |||||
engine.reset_points() # Reset the engine to study other points | |||||
assert power.shape == (256, ) | |||||
print_success(' - Test 2 "Use ELMO Engine": Success!') | |||||
######################################################### | |||||
# TEST 3 : USE A REAL SIMULATION # | |||||
######################################################### | |||||
from elmo import get_simulation | |||||
KyberNTTSimulation = get_simulation('KyberNTTSimulation') | |||||
simulation = KyberNTTSimulation() | |||||
simulation.set_challenges(simulation.get_random_challenges(10)) | |||||
res = simulation.run() | |||||
assert not res['error'] | |||||
assert res['nb_traces'] == 10 | |||||
assert res['nb_instructions'] | |||||
print_success(' - Test 3 "Use A Real Simulation": Success!') | |||||
######################################################### | |||||
print_success('All seems fine!') |