This work was done in the course “Advanced Security Engineering Lab” from Prof. Dr. Katzenbeisser in WS2019/20 at the University of Passau. It was accomplished in collaboration with Chris Weibel, Korbinian Spielvogel, Felix Benasch and Felix Klement.

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 passengers 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.

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

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:

    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 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

        candump can0
  2. CAN-FD Shield picanfd-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:

      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:

      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

        cansend can0 7df##05555555555555555
      • Listen on the CAN-FD-Bus

        candump can0
    • 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:

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

$ 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:

$ ./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:

$ 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:

$ 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':
$ 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':
$ 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)
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:

$ 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:

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:

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.

make compile

To conveniently flash the different nodes one can call one of these (or multiple in different terminals):

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:

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.

Insturmentcluster pinout

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.

280#490E+ RPM +00H0E001B0E

Disable airbag:

050#0080000000000000

Disable airbag + show seat belt error

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 speaker

Brainstem Setup

To setup the brainstem follow the instructions which are given in the file here.

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:

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:

python3 CAN_Gateway.py

or

python3 CAN_FD_Gateway.py

Config

  • For log output you can set DEBUG_CAN (to get CAN logs) and DEBUG_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 the CAN_Gateway.py or CAN_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 of connected_devices.txt must look like this: 2 3 7 1

Implementation Overview

Files

The source code is organized as follows.

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 Sequence diagram

  2. Second step - Session-Key establishment Sequence diagram

  3. Third step - Group-Key establishment Sequence diagram

  4. Fourth step - Send RPM message to instrument cluster Sequence diagram

main.c initializes and runs the automaton which is defined in components/automaton/automaton.c. Our sample application is as follows:

  1. 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
  2. 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
  3. 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
  4. 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)

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.)

All layers

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

  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

#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

#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
#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

#define CONF_LOG_KEYSHARE 1

Prints generated keys and key material to stdout.

#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
#define AUTOMATON_NUM_NODES 5

Total number of devices taking part in the key scheme.

Maximum number of memebers in one key group
#define MAX_NUMBER_OF_MEMBERS 3

Max number of devices in one group-key group.

Number of key groups
#define NUMBER_OF_GROUPS 2

Total number of groups, each with their own key.

Configuration specific to our setup

#define RASPI_TACHO_CONTROL_PARTNER_ID 5

Device ID of the Raspberry Pi that guards access to the instrument cluster.

Only used in /payload/.

#define CAN_ID_RPM 0x280

CAN-ID that controls the RPM needle of our instrument cluster.

Only used in /payload/.

#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

#define GATEWAY_PORT 4444

Port used for communication with and between gateways (GATEWAY_PORT).

#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. Further details on the values can be found in the measurement file itself.

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