An increasing number of external interfaces allows adversaries to virtually break into modern road vehicles. For this reason, researchers came up with multiple security frameworks for in-vehicle networks, in order to prevent intruders from imposing safety threats on passengers. In particular, authentication has been added to in-vehicle traffic, such that forged messages can be detected. Additionally, encryption is applied on specific message frames, if the passenger’s privacy were in danger. However, efficient means to distribute cryptographic keys on heterogeneous automotive systems are often missing. The goal of this project thus was to build an exemplary in-vehicle CAN network and provide a proof-of-concept implementation for implicit certificates for a CAN-bus based system.
Outcome of the project: While in the current state-of-art, any connected device on the CAN-bus would be able to issue requests to the car-control board, thus being able to control the speed, brakes, RPM etc. of the car, which could lead to devestating outcomes, my colleague and I managed to implement an efficient proof-of-concept to exclude such potential attackers from the CAN-bus based system by means of a smart key-distribution scheme and implicit certificates. In the short video on the top, it can be seen that the first device (valid network node) can successfully send valid data/messages to the car-control board. However, the second device’s messages (a device that connected after the network configuration and the system boot) are ignored, as it is recognized as an potential attacker.
If you want to know more about this project, feel free to contact me.
The following documentation provides an overview of the project:
Hardware Setup
CAN-Bus
We connect all the nodes by means of a selfmade twisted pair wire. Both ends of the bus are terminated with one 120 Ohm resistor each. The connection is H-H-H and L-L-L (All CANH are connected and all CANL are connected).
In case nodes are intended to be designated the “end of the bus” nodes permanently, one can also solder a connection on the board to use an 120 Ohm resistor which already exists on all of the boards. This feature was not used in our implementation and is thus open for future use if wanted.
CAN Bus Setup using a breadboard and jumping wires. Further, two 120 Ohm resistors are needed at the end of each breadboard.
Setup RaspberryPi with CAN/CAN-FD Shield
General Raspbian Setup
1) Copy a raspbian image to an SD card
2) If SSH is not enabled by default, you can enable it by creating an empty text file called “ssh” in the raspbian boot directory on the SD card.
3) If HDMI is needed, you have to add following two lines in the /boot/config.txt
:
hdmi_force_hotplug=1
hdmi_drive=2
4) To enable static IP addresses modify the file /etc/dhcpcd.conf
as follows:
1
2
3
4
interface eth0
static ip_address=<IP>
static routers=<IP of router> [Optional]
static domain_name_servers=<IP of DNS> [Optional]
CAN/CAN-FD Shield extensions for the Raspberry Pi
1) CAN Shield
Raspberry Pi 3 B+ with a PiCAN-Shield
- Modify the
/boot/config.txt
file:dtparam=spi=on dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25 dtoverlay=spi-bcm2835-overlay
-
Afterwards put following line into the
/etc/rc.local
file to bring up the CAN interface automatically:
sudo /sbin/ip link set can0 up type can bitrate 500000
- Install CAN-Utils
- Reboot
- Test the installation:
-
Send a single CAN-Frame
cansend can0 7DF#0201050000000000
- Listen on the CAN-Bus
1
candump can0
2) CAN-FD Shield
Raspberry Pi 3 B+ with a PiCAN-FD-Shield
- Download the kernel patch from
https://www.dropbox.com/s/n0s31lvgfe8enb6/mcp2517fd-rasp1%2B2%2B3_working.tar?dl=0
- Copy the file to the root directory then untar the files:
- sudo cp mcp2517fd-rasp1+2+3_working.tar /
- cd /
- sudo tar –xf mcp2517fd-rasp1+2+3_ working.tar
- Modify the
/boot/config.txt
file:1 2 3 4 5 6 7 8
core_freq=250 kernel=ms7/zImage device_tree=ms7/bcm2710-rpi-3-b.dtb overlay_prefix=ms7/overlays dtoverlay=mcp2517fd-can0 dtparam=interrupt=25 dtparam=oscillator=40000000 dtparam=i2c_arm=on
- Afterwards put following line into the
/etc/rc.local
file to bring up the CAN interface automatically:1
sudo /sbin/ip link set can0 up type can bitrate 500000 dbitrate 2000000 fd on sample-point .8 dsample-point .8
- Install CAN-Utils
- Reboot
- Test the installation:
- Send a single CAN-FD-Frame
1
cansend can0 7df##05555555555555555
- Listen on the CAN-FD-Bus
1
candump can0
- Send a single CAN-FD-Frame
- IMPORTANT - When sending multiple CAN-FD messages, the linux kernel driver for the CAN-FD shield crashes, thus the CAN-interface won’t respond until you reboot the whole device. This makes the current state of the shield unusable for the project. The bug is currently under investigation.(Latest status can be found here: https://github.com/msperl/linux-rpi/issues/6)
ESP Dependencies and Setup
This section describes how to get to run our hard and software.
Install Software Dependencies
The Xtensa Toolchain and ESP-IDF are required in addition to a set of libraries and tools available in many Linux distributions.
On Ubuntu, one can do the following:
1
2
3
4
5
6
7
sudo apt install gcc git wget make libncurses-dev flex bison gperf python python-serial
wget https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
tar xf xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
git clone -b v3.1.1 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
git checkout a62cbfec9ab681bddf8f8e45d9949b1b1f67a2ec
git submodule update
ESP32 USB Device Configuraiton
Step 1
It is recommended to make the boards available under a constant and recognizable
name by using the supplied udev-rules (see seceng-can-implicit.rules
for
details).
To allow the current user to access USB devices directly, add the user to the dialout group, e.g. by using
1
$ sudo adduser $(id -un) dialout
Step 2
In order to work with upcomming commands/setup you must use the atalla USB 3.0 Hub
with the devices plugged in from top to bottom.
First of all you need to execute the find_device_names.sh
script. This will produce the following output:
1
2
3
4
5
6
7
$ ./find_device_names.sh | grep "USB"
/dev/bus/usb/002/039 - 1a86_USB2.0-Serial
/dev/ttyUSB1 - 1a86_USB2.0-Serial
/dev/ttyUSB2 - 1a86_USB2.0-Serial
/dev/bus/usb/002/040 - 1a86_USB2.0-Serial
/dev/bus/usb/002/038 - 1a86_USB2.0-Serial
/dev/ttyUSB0 - 1a86_USB2.0-Serial
Now you need to identifiy which of the usb devices matches your physical usb hub. In our case it’s the /dev/ttyUSB0
, /dev/ttyUSB1
, /dev/ttyUSB2
, as we have plugged in three ESP32
devices.
Afterwards you can get the needed information for setting up the udev-rules
by executing following command:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ udevadm info --attribute-walk --name=/dev/ttyUSB0
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.1':
KERNELS=="2-3.1.1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="ff"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="8"
ATTRS{bMaxPower}=="98mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bcdDevice}=="0263"
ATTRS{bmAttributes}=="80"
ATTRS{busnum}=="2"
ATTRS{configuration}==""
ATTRS{devnum}=="38"
ATTRS{devpath}=="3.1.1"
ATTRS{idProduct}=="7523"
ATTRS{idVendor}=="1a86"
ATTRS{ltm_capable}=="no"
ATTRS{maxchild}=="0"
ATTRS{product}=="USB2.0-Serial"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="unknown"
ATTRS{rx_lanes}=="1"
ATTRS{speed}=="12"
ATTRS{tx_lanes}=="1"
ATTRS{urbnum}=="13"
ATTRS{version}==" 1.10"
Inside of the output you need to look for the idProduct
(7523) as well as the idVendor
(1a86). Futhermore you need the devpath information for each single usb device. You can get this by executing the following commands:
1
2
3
4
$ udevadm info --attribute-walk --name=/dev/ttyUSB0 | grep "looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/"
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.1/2-3.1.1:1.0/ttyUSB0':
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.1/2-3.1.1:1.0':
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.1':
1
2
3
4
$ udevadm info --attribute-walk --name=/dev/ttyUSB1 | grep "looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/"
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.2/2-3.1.2:1.0/ttyUSB1':
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.2/2-3.1.2:1.0':
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.2':
1
2
3
4
$ udevadm info --attribute-walk --name=/dev/ttyUSB2 | grep "looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/"
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.3/2-3.1.3:1.0/ttyUSB2':
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.3/2-3.1.3:1.0':
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.1/2-3.1.3':
You can extract the dev path by looking at the last digit of the device’s output.
With this information you can now create the udev-rules
.
UDEV-Rules: (seceng-can-implicit.rules
)
1
2
3
4
5
6
7
8
SUBSYSTEM=="tty", ATTRS{idProduct}=="7523", ATTRS{idVendor}=="1a86", \
ATTRS{devpath}=="*.1", SYMLINK+="seceng-can-implicit-b1"
SUBSYSTEM=="tty", ATTRS{idProduct}=="7523", ATTRS{idVendor}=="1a86", \
ATTRS{devpath}=="*.2", SYMLINK+="seceng-can-implicit-b2"
SUBSYSTEM=="tty", ATTRS{idProduct}=="7523", ATTRS{idVendor}=="1a86", \
ATTRS{devpath}=="*.3", SYMLINK+="seceng-can-implicit-b3"
After adjusting everything inside of the seceng-can-implicit.rules
file you need to copy that into your local dev-rules:
1
$ cp seceng-can-implicit.rules /etc/udev/rules.d
Keep in mind that the device order in the usb hub must NOT be changed after configuring the udev-rules.
Environment Variables
Edit config.mk
and provide the variables PATH
and IDF_PATH
.
If you downloaded the files like suggested above, the following might work:
1
2
PATH := $(PATH):./esp/xtensa-esp32-elf/bin
IDF_PATH := $(PATH):./esp-idf
Alternatively, provide absolute paths as can be seen in the existing
config.mk
for our systems.
You should be able to call make export
and see two export statements for
defining the variable in the current shell if wanted.
Optional: Test Compiling and Flashing with “Hello World”
The “Hello World” example supplied with ESP-IDF can be invoked by calling
the two export
statements from the previous section and continuing as follows:
1
2
3
4
5
6
7
8
9
10
export IDF_PATH=...
export PATH=...
cp -r $IDF_PATH/examples/get-started/hello_world .
cd hello_world
# Set the device name to flash to. It can be /dev/seceng-can-implicit-b1 if
# the udev rules are in place or something like /dev/ttyUSB0 otherwise.
make menuconfig
make -j8 all
make flash
make monitor
Compiling and Running
Because we have nodes with different behaviour, our code is compiled three
times, each time with a different value for this_node_id
.
First, generate_ca_key/main.c
is compiled and run. This writes a
public-private keypair to be used as the CA_KEY
to the file main/ca_key.c
.
The code for all nodes then has variables ca_secretkey
and ca_publickey
.
The secretkey is 0 for all but the CA node which is defined as the node
with THIS_NODE_ID
equal to 1.
Our compilation process can be invoked by running.
1
make compile
To conveniently flash the different nodes one can call one of these (or multiple in different terminals):
1
2
3
make flash-1
make flash-2
make flash-3
For the program to run properly, it is required that all nodes run the program thus all need to have been flashed. It is not required to simultaneously attach to all the programs although it provides a good overview. If the terminal multiplexer TMUX is available, one can also call the following to flash all nodes and to attach to all of them at once:
1
make flash-all-tmux
Note that in our tests we observed varying quality of the stability of the serial connections: With one laptop (Dell Latitude 7404) it was impossible to get the flashing process correct, with a desktop machine (HP compaq d330m) the connection was slightly unreliable and with another laptop (HP Spectre x360 Convertible) it worked perfectly well all the time. It remains unclear if this is a hardware or OS issue (cables could be ruled-out as the source of the issue, the impact of the hub was negligible).
Instrument cluster Setup
We have a Volkswagen Polo 6R instrument cluster. The pin layout of it can be seen down below. To establish the CAN-bus functionality, we connected the CAN-High and CAN-Low of the instrument cluster accordingly to our breadboard. The power supply 12V and the ground pin were wired to a 12V battery to power the hardware component. After that the cluster is ready to go.
Pin Layout of the VM Polo Instrument Cluster
CAN Codes
Due to time constraints we did not have enough time to get the complete cluster up and running. Below are the commands we were able to reverse engineer.
RPM
RPM = value for rounds per minute instrument
The value 10 corresponds to value 1000 on the instrument cluster.
1
280#490E+ RPM +00H0E001B0E
Disable airbag:
1
050#0080000000000000
Disable airbag + show seat belt error
1
050#0080040000000000
Resources
In order to get the correct Message ID with its corresponding payload we basically relied on information other people published on the internet. You can find a nice project from Leon Bataille on Hackaday where he is trying to connect the instrument cluster with an Arduino and a CAN BUS shield and then control it with the Telemetry API of Euro Truck Simulator 2 (Ref.: https://hackaday.io/project/6288-can-bus-gaming-simulator ). Another good source for understanding how the instrument cluster works is a video from the automotive security channel on youtube (Ref.: https://www.youtube.com/watch?v=4SgW64d_fbE)
Sound module
In order to get rid of the annoying beeping sound that the instrument cluster produces after a few seconds, we simply removed the loudspeaker in the cluster. To do that we opened the cluster. The speaker is located at the back on the upper left side (see red marking on the picture down below). To install the sound module back in, you can simply snip it back into it position. No soldering or other wiring is needed
Placement of the speaker in the VW Polo Instrument Cluster
Speaker removed from the Instrument Cluster
Brainstem Setup
If you need the instructions to setup the Brainstem, feel free to contact me via mail.
Gateway Setup
Installation
For the gateway you need a Raspberry Pi with a CAN/CAN-FD Shield installed. The device itself has to be connected to a switch via ethernet as well as to the breadboard (CAN-bus) via the CAN-Pins of the shield.
Afterwards, you have to copy the file CAN_Gateway.py
or CAN_FD_Gateway.py
onto the device. The required packages can be installed by executing:
1
pip3 install -r requirements.txt
Keep in mind that you need internet connection on the Raspberry Pi in order to be able to install the packages. The code can then be started with:
1
2
3
4
5
python3 CAN_Gateway.py
or
python3 CAN_FD_Gateway.py
Config
- For log output you can set
DEBUG_CAN
(to get CAN logs) andDEBUG_ETHERNET
(tp get Ethernet logs) to 1. - If more than 1 gateway is used, the variable
OTHER_GATEWAYS
has to be defined in such a way, that it contains all IP addresses of the other gateways in the network - The Raspberry Pis need a file called
connected_devices.txt
in the same directory as theCAN_Gateway.py
orCAN_FD_Gateway.py
file. It must contain all IDs of the connected CAN devices on the CAN-Bus which the gateway lies on.
E.g if the Gateway is connected to a CAN-Bus with Devices 2,3,7,1, the content ofconnected_devices.txt
must look like this:1 2 3 4
2 3 7 1
Implementation Overview
Files
The source code is organized as follows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
hal
├── can
│ ├── esp_can
│ │ ├── component.mk
│ │ ├── hal.c
│ │ └── hal_get_time.c
│ ├── hal.h
│ ├── msg.c
│ └── pi_can
│ ├── hal.c
│ └── hal_get_time.c
├── can_fd
│ ├── hal.h
│ ├── msg.c
│ └── pi_can_fd
│ ├── hal.c
│ └── hal_get_time.c
└── ethernet
├── hal.h
├── linux_ethernet
│ ├── hal.c
│ └── hal_get_time.c
└── msg.c
components
├── automaton
│ ├── automaton.c
│ ├── component.mk
│ ├── include
│ │ ├── autoconf.h
│ │ ├── automaton.h
│ │ ├── conf.h
│ │ ├── enum.h
│ │ ├── hal_get_time.h
│ │ ├── msg.h
│ │ ├── myassert.h
│ │ ├── payload.h
│ │ └── secure_send.h
│ └── secure_send.c
├── bearssl
│ ├── ...
└── crypto
├── aead.c
├── component.mk
├── ec.c
├── ecdh.c
├── ecqv_ca.c
├── ecqv_client.c
└── include
├── aead.h
├── ec.h
├── ecdh.h
├── ecqv_ca.h
└── ecqv_client.h
payload
├── brainstem.c
├── component.mk
├── esp_button.c
└── raspberrypis.c
Demonstration Program
1) First step - Certificate establishment
1) Second step - Session-Key establishment
1) Third step - Group-Key establishment
1) Fourth step - Send RPM message to instrument cluster
main.c
initializes and runs the automaton which is defined in
components/automaton/automaton.c
. Our sample application is as follows:
- Establish an implicit certificate between each node and the CA
- Starting with the CA, every node performs the following operations:
- Create a certificate signing request and send it to the CA (
CERT_REQ
) - CA calculates the certificate signing response
- Receive implicit certificate from CA (
CERT_RESP
)- The CA can calculate its own certificate itself without any communication
- Tell the next node it may run (
YOUR_TURN
) - If the next node is the CA, every node has established an implicit certificate
- Create a certificate signing request and send it to the CA (
- Starting with the CA, every node performs the following operations:
- Establish a session key between each node and the CA
- CA has (
YOUR_TURN
), skips the session key establishment with itself and sends (YOUR_TURN
) to the next node - Starting with the next node, every node performs the following operations:
- Create a session key request and send it to the CA (
SESSION_KEY_REQUEST
) - CA calculates the session key
- Receive CA’s keyshare (
SESSION_KEY_RESPONSE
) - Calculate session key
- Tell the next node it may run (
YOUR_TURN
) - If the next node is the CA, every node has established a session key with the CA
- Create a session key request and send it to the CA (
- CA has (
- Establish group keys for every can group
- CA has (
YOUR_TURN
), the following operations are performed:- CA sends (
GROUP_KEY_REQUEST
) to the first group member - First group member creates its keyshare and sends it to the CA (
GROUP_KEY_SHARE
) - CA stores the keyshare
- CA sends (
GROUP_KEY_REQUEST
) to the next group member- If the key shares of all group members are present
- CA calculates the group key and sends it to the first group member (
GROUP_KEY
) - First group member receives the encrypted group key and sends (
GROUP_KEY_RECEIVED
) to the CA - CA sends the encrypted group key to the next group member (
GROUP_KEY
)- If all group members have confirmed the group key reception
- CA sends (
GROUP_KEY_REQUEST
) to the first member of the next group- If all group keys have been established and distributed
- CA sends (
DO_ACTUAL_STUFF
) to the next node
- CA sends (
- CA has (
- Enter communication loop
- Starting with the next node, every node performs the following operations:
- Send (
DO_ACTUAL_STUFF
) to the next node - Enter communication loop (Now the ECUs, in our case the ESP32 devices, respond to button presses by the user. These trigger the transmission of an RPM message to the instrument cluster. Furthermore this message also contains the HMAC of the message that was generated by using the previously established group key)
- Send (
- Starting with the next node, every node performs the following operations:
Message Layers
Our code is logically divides into four layers. The security layer contains all functionalities regarding cryptography. The application layer includes the automaton.c, which is responsible for the program logic management. The protocol layer is the actual implementation of the communication protocol (e.g CAN, CAN-FD, Ethernet etc..). And the last layer, the hardware layer, contains the device specific implementations for the protocol (e.g CAN for esp, CAN for raspberry pi etc.)
Messages are communicated through different layers as seen in the figure. This way, one can replace the protocol / hardware layer as well as re-use it in different contexts/applications. Especially, it is possible to replace the CAN protocol with any other protocol.
Protocol Abstraction Layer (PAL)
Hardware Abstraction Layer (HAL)
Interaction between HAL - PAL
CAN-Frame Layouts
We modified the CAN-Frame layout such that it contains more meta-data that is needed for the communication between devices. Firstly, we added a sequence number, so that we can identify subsequent messages, if a message has to be fragmented into several CAN-Frames. Furthermore, we needed the meta-data for sender and receiver, so that the CA (Brainstem), can communicate with specific devices. In case of a first message, we also had to add a field for the message length, so that the receiving device knows how much data it can expect.
First message
Subsequent messages
Cryptography Layers
1
2
3
4
5
6
7
8
9
10
11
Location Features used
+----------------------------------+
| Application Layer | CA--Client interaction, Key Exchange,
| components/automaton/automaton.c | Encrypted and Authenticated channel
+----------------------------------+
| High-Level Primitive | ECQV, ECDH, AEAD abstraction
| components/crypto |
+----------------------------------+
| Cryptography Library BearSSL | EC-Cryptography, AEAD implementation
| components/bearssl |
+----------------------------------+
Our application uses the BearSSL library for all cryptography primitives. In
order to implement ECQV we had to add some code which is in components/crypto
together with small abstraction layers for ECDH and AEAD such that the
high-level layer becomes largely independent of the library in use.
Additionally, this should allow for simplified re-use of ECQV in other applications.
Building and Configuration
The code base contains code for several devices, all of which can be built using their respecive build targets from the makefile. Note that each device needs a distinct device ID which will be used to identify the device in the network later.
This device ID is stored in node_device_id.h file, which is generated automatically from the makefile. Thus the device IDs are currently hardcoded in the makefile.
Compile Targets
compile
Builds the code for the ESP32 boards, same code for three separte devices, ie the same hardware but with different device IDs.
compile_brainstem
Builds the brainstem, aka CA. The binary is written to build/brainstem.
Device ID defaults to 1.
cross_compile_brainstem
Allows cross-compiling the brainstem on a x64/x86 Ubuntu Linux machine for the required ARM architecture of the actual board.
compile_raspi
Compiles the code for Raspberry Pis with a CAN shield. Builds one binary to build/raspi-can.
Device ID defaults to 5.
compile_raspi_fd
Compiles the code for Raspberry Pis with a CAN-FD shield. Builds one binary to build/raspi-canfd.
Device ID defaults to 6.
Configuration
Almost all configuration is done in components/automaton/includes/conf.h
Key distribution
1
#define CONF_MEASURE_TIME 1
Configures enabling of timing measuresments for certificate/sessionkey/groupkey distribution.
1 for enabled, 0 for off.
IDs used for our setup
1
2
3
4
5
#define ESP_1 2
#define ESP_2 3
#define ESP_3 4
#define PI_1 5
#define PI_2 6
Lists the default IDs for our setup, this is only used in /payloads/ to specify the correct payload for each device.
HMAC size
1
#define HMAC_BYTES 8
Specifies the size of the HMAC used for authenticating, in bytes.
Default is 8 bytes, maximum 32 bytes.
In either case the HMAC uses SHA256 hashing.
Logging
1
#define CONF_LOG_KEYSHARE 1
Prints generated keys and key material to stdout.
1
2
3
4
#define CONF_LOG3 0
#define CONF_LOG2 0
#define CONF_LOG4 0
#define CONF_LOG1 0
Different logging objectives. CONF_LOG1 usually is sufficient.
Total number of devices
1
#define AUTOMATON_NUM_NODES 5
Total number of devices taking part in the key scheme.
Maximum number of memebers in one key group
1
#define MAX_NUMBER_OF_MEMBERS 3
Max number of devices in one group-key group.
Number of key groups
1
#define NUMBER_OF_GROUPS 2
Total number of groups, each with their own key.
Configuration specific to our setup
1
#define RASPI_TACHO_CONTROL_PARTNER_ID 5
Device ID of the Raspberry Pi that guards access to the instrument cluster.
Only used in /payload/.
1
#define CAN_ID_RPM 0x280
CAN-ID that controls the RPM needle of our instrument cluster.
Only used in /payload/.
1
#define GROUP_ID_RPM 0
Group ID of the group that has access to controlling the RPM needle.
Only used in /payload/.
Ports For Ethernet Socket Communication
1
#define GATEWAY_PORT 4444
Port used for communication with and between gateways (GATEWAY_PORT).
1
#define PASS_OVER_PORT 4444
Port used for communication between the Raspberry Pi connected to the network and the Raspberry Pi driving the Instrument cluster.
Setting Groups And Participants In The Code
Although the longterm plan is to have groups and participants in a configuration file, currently they are hardcoded in components/automaton/automaton.c.
How to start the program/ protocol
1) Compile the code for the brainstem with following command:
make compile_brainstem
2) Compile the code for the ESP32 devices:
make compile
3) Compile the code for the Raspberry Pis (ECUs on CAN-Bus, not Gateway):
make compile_raspi
4) Start the Gateway code (on all gateways)
python3 CAN_Gateway.py
5) Flash & monitor the ESP32 devices:
make flash-all && make monitor-all
6) Start the code on the Raspberry Pis that are connected to the CAN-Bus:
build/raspi-can
7) Wait till all devices are ready (=show their Node ID)
8) Start the brainstem:
build/brainstem
9) When the protocol is done (=every device within a group shows its group key), you can start pressing the buttons on the ESP32 devices to send RPM commands to the Instrument Cluster
Adding support for a new protocol
In order to add support for a new protocol, create a new folder in /hal/<protocol>> for it and add an implementation of components/automaton/include/msg.h. In the make file, duplicate one of existing build targets and adjust the path to /hal/<protocol>.
Adding support for a new hardware device / OS to an existing protocol
The application comes with support for can, can-fd and Ethernet protocols, all of which can work on many different hardware devices or operating systems. For the three protocols mentioned, the code in /hal/<protocol> makes use of an hardware abstraction lay (hal). For example, /hal/can/esp32/ implements can for the esp32 hardware, while /hal/can/pi_can/ implements can for raspberry pis with can-shields. Both make use of the msg.h implementation for can though. In order to add support for more can-hardware, create a subfolder in /hal/can/<newhw> and implement /hal/can/hal.h in it.
The same applies to can-fd and Ethernet protocols.
The last step is again to duplicate an existing build target in the makefile and adjust paths to the newly added code.
Performance Considerations
In terms of performance one can consider the message length overhead and computation time. Both can be assessed in terms of their real-world performance (i.e. including necessary overhead for waiting and metadata) or their kernel performance (e.g. running only a crypto primitive without interferences that would usually appear in practice).
Kernel Length Overhead
Compared to an unauthenticated setup, the following overheads exist in terms of message lengths:
-
CA Public keys need to be provided to the client nodes. We do this by compiling-in the CA key. If one were to implement this differently, an overhead of at least the key’s length would follow. For our choice of the P-256 curve, this key has length 65 bytes and needs to be distributed to the number of nodes
-
Certificate Signing Requests. A CSR needs to be generated for each implicit certificate to be requested from the CA. In our setup these happen once per system startup and node. The CSR size is again a public key and thus another 65 bytes.
-
CSR Responses. The CA answers a CSR by a public implicit certificate (length 65 bytes) and a secret value of length 32 bytes.
-
ECDH Messages. To derive a symmetric key, all nodes perform a point-to-point ECDH key exchange with all nodes they want to communicate with (worst case: everyone talks to everyone this means this happens (n - 1) * n times). The ECDH key exchange consists of two public keys being sent across the message channel, i.e. 2x65 bytes.
-
AEAD Overhead. To communicate using their symmetric key, nodes invoke an AEAD scheme provided by AES-GCM. The message overhead from AES-GCM is as follows: 12 bytes initialization vector + 16 bytes tag
-
In practice, our messages need to be fragmented to be transmitted across the CAN bus. A CAN frame can transmit up to 8 bytes. We encode 2 byte for the sequence number, the sender and the receiver in each message. Furthermore, each first message has an additional 2 byte for the message length.
- For every group member and additional request for input has to be sent.
- Every group member has to randomly generate 16 Byte of input for the Group key. Furthermore, this input has to be transmitted to the CA.
- The CA has to gather all inputs from the group members to generate a single group key. This group key has then to be distributed to all group members, resulting in an additional message for every group memebrs
- The last three mentioned overheads have to be repeated for every group (CAN-ID)
- After the group key has been established & distributed, all subsequent messages have the HMAC of the message appended. The HMAC length can be changed. However, there is a tradeoff between security (e.g short HMACs might be bruteforcable) and performance (e.g long HMACs have to be fragmented to several messages [6 byte payload per CAN-Frame])
Messaging Overhead
The frames of message overhead are as follows: YOUR_TURN + ($\text{CERT}_{\text{REQ}}$ + $\text{CERT}_{\text{RESP}}$ + YOUR_TURN) $\cdot$ (nodes - 1). This amounts to the following number of frames: 1 + 23 $\cdot$ (nodes - 1). The individual values are also in the table.
Message | Content | CAN Frames |
---|---|---|
YOUR_TURN | 1 | |
$\text{CERT}_{\text{REQ}}$ | EC point | 9 |
$\text{CERT}_{\text{RESP}}$ | EC point, EC scalar | 13 |
Timing
For the evaluation of the application, we’ve come up with several test-scenarios that show the impact of the setup variables on the duration of the group-key establishment protocol. The experiements were repeated 5 times for each setting (the number of measurements might seem pretty low for an evaluation but as we have dedicated test network for the experiments i.e no other communication, no other devices, no noise etc, we consider it to be sufficient). Afterwards, we computed the average of the results i.e the following measurement values within the tables are the average values of the respective timings in milliseconds (ms). In general we can say, that the measured values were very constant, so there is a low deviation.
Increasing the member number per group (1 Group, 2 Gateways, 5 Nodes)
Increasing the member number per group while having a constant number of groups (1), gateways (2) and participating devices (5). The results show that the group-key time increases linearly, which is quite obvious, as for every member of the group an additional request for the group-key input has to be sent. Furthermore, the CA has to send one additional message, containing the group key, for each additional member in the group.
Category | 1 Member | 2 Member | 3 Member |
---|---|---|---|
Certificate Time | 1269.2 | 1293.6 | 1295.0 |
Session Key Time | 1111.8 | 1102.0 | 1103.3 |
Group Key Time | 41.4 | 103.0 | 134.8 |
Total Time | 2475.6 | 2573.8 | 2589.5 |
Increasing the number of groups (3 Members per group, 2 Gateways, 5 Nodes)
Increasing the number of groups while having a constant group member number (3), gateways (2) and participating devices (5). Our guess was, that the group-key time here also increases linearly, as for every additional group, the CA has to execute an additional round of group-key generation. The results confirm this thesis. Furthermore, our experiments have shown, that the ESP32 devices can handle at maximum 163 groups each with 3 members, before it runs out of memory.
Category | 1 Group | 2 Groups | 5 Groups | 10 Groups | 50 Groups | 100 Groups |
---|---|---|---|---|---|---|
Certificate Time | 1295.0 | 1300.4 | 1299.8 | 1291.2 | 1274.6 | 1286.2 |
Session Key Time | 1103.3 | 1118.8 | 1121.8 | 1114.6 | 1116.2 | 1152.8 |
Group Key Time | 134.8 | 304.8 | 848.4 | 1691.4 | 8518.0 | 17255.8 |
Total Time | 2589.5 | 2776.2 | 3324.6 | 4150.2 | 10961.4 | 39609.8 |
Measuring the network effect
In these test scenarios, we wanted to see whether the distribution of devices across multiple networks (in our case 2) had an impact on performance. Since additional communication messages are needed, e.g. if the YOUR_TURN message is sent to the next device via Ethernet instead of simply publishing it on the same CAN bus, we thought that this would have a drastic effect. However, the results have shown that it only has a minor impact on the performance. This means, that several CAN-Buses can be efficiently connected via Ethernet.
1 Device (1 Member, 1 Group)
Category | 1 Network (1 Device) |
---|---|
Certificate Time | 425.0 |
Session Key Time | 292.0 |
Group Key Time | 43.0 |
Total Time | 768.0 |
2 Devices (2 Members, 1 Group)
Category | 1 Network (2 Devices) | 2 Networks (1 Device per Network) |
---|---|---|
Certificate Time | 741.0 | 753.6 |
Session Key Time | 598.0 | 608.0 |
Group Key Time | 80.8 | 106.6 |
Total Time | 1428.6 | 1504.2 |
4 Devices (2 Members, 1 Group)
Category | 1 Network (4 Devices) | 2 Networks (2 Device per Network) |
---|---|---|
Certificate Time | 1285.0 | 1302.4 |
Session Key Time | 1107.6 | 1111.8 |
Group Key Time | 83.2 | 110.0 |
Total Time | 2486.4 | 2597.0 |
Increasing the number of devices
The last test-scenario is about measuring the impact on the performance when increasing the number of devices. The settings were set to 1 network, 2 group members, 1 group. Our results show, that the certificate time as well as the session key time increase linearly as the number of devices on the network is increased. This is due to the fact that the Cert and Session key establishment has to be done for every device i.e for each additional device, this process has to be executed an additional time. One now might ask why the group key time also changes when increasing from the number from 1 to 2 devices. Due to the outbreak of the coronavirus in Passau, we only had several measurements left. Therefore, our first measurement with 1 device has 1 member per group, while the other two measurements were done with 2 members per group. Usually, if the group member number is kept constant, the group key time is also constant.
Category | 1 Device | 2 Devices | 4 Devices |
---|---|---|---|
Certificate Time | 425.0 | 741.0 | 1285.0 |
Session Key Time | 292.0 | 598.0 | 1107.6 |
Group Key Time | 43.0 | 80.8 | 83.2 |
Total Time | 768.0 | 1428.6 | 2486.4 |
Useful Links
-
ESP IDF Documentation: https://docs.espressif.com/projects/esp-idf/en/stable/get-started/index.html#get-started-get-esp-idf
-
ESP IDF Repository: https://github.com/espressif/esp-idf
-
OLIMEX Resources for the Boards: https://github.com/OLIMEX/ESP32-EVB