vt-ipc 1.0.0
Set of utilities implementing DHAULGIRI's IPC protocol
Requires Python: <=3.13.2,>=3.10

# dhaul_protobuf

Content:

- Protobuf definitions for the Dhaulagiri IPC layer.
- C and python output
- Python module implementing the DHAUL ipc protocol, and related tools

## Development Standard

All development shall follow the Dhaulagiri development standard: <https://vitalthings.atlassian.net/wiki/spaces/DHAUL/pages/267288577/DHAUL+-+SW+Development+Plan>

Branch types: main and feature branches. Release / project branches if necessary.
Protected main branch. Only PR allowed.
Code Review from at least 1 team member required
Pipeline must pass for PR
Branch names: feature/DHAUL-XXXX

## Linting 

All linting and formatting tools can be run at once by using [pre-commit](https://pre-commit.com/):

```sh
$ pre-commit run --all-files
```

Or better, the pre-commit hook can be installed to run automatically on every commit:

```sh
$ pre-commit install
```

For formatting protobuf definitions [buf format](https://buf.build/blog/introducing-buf-format) is used.

For formatting and linting Python code the following tools are used: [black](https://github.com/psf/black), [isort](https://pycqa.github.io/isort/), [flake8](https://flake8.pycqa.org/en/latest/), and [docformatter](https://pypi.org/project/docformatter/).



## Running Python IPC tools

Currently, the IPC library setup is mostly based off `dhaul_openapi` project, and also utilizes poetry for dependency management.

### Running SQLDB database-util

After installing the project with `poetry`, run
```
poetry run database --help
```

To see available options for the tool.

## Release process

- checkout a feature branch
- Modify the protobuf definition file(s) in the definitions folder
- Commit and push
- The pipeline in this repo will generate protobuf source files for python and
  c and automatically make a commit placing the result in the ipc_protobuf folder
- code review and merge feature branch into main.
- Add this repo as a submodule in the consuming repository (if not already done) and track the appropriate commit

## Provisioning tool

![Provisioning tool screenshot](./docs/provisioning_tool_screenshot.png)

The provisioning tool is a PC tool allows for reading and writing network (including TLS certificates) to devices over USB. It also allows viewing the current SW version and real-time network status. It is a GUI tool written in Python and using Qt.

### Installation

The provisioning tool needs some dependencies for the GUI, which are defined in `pyproject.toml` as an optional group. To install them run:

```sh
$ poetry install --with=provisioning_tool
```

In WSL, the following might also be needed if there is an error about xcb-cursor0 when launching:

```sh
sudo apt install libegl1 libxcb-cursor0
```

## Usage

To run the tool invoke:

```sh
$ poetry run provisioning-tool-gui
```

A window with two parts should appear. The left side contains the network configuration editor and the right side displays the status of the connected device.

To start using the tool, first connect the device to the PC via USB. Then select the appropriate serial port in the top right drop down menu. Then hit connect to connect to the device. The "Versions" and "Network status" tables should be populated with data from the device.

Then hit the "Read from device" button on the right side to import the configuration from the device. The network configuration editor should be populated with the data from the device. You can now edit the configuration by interacting with the text fields and checkboxes. Press "Write to device" to write the configuration to the device. You should see the network status updating as the credentials were entered.

### JSON import/export

The tool supports importing and exporting the network configuration in JSON format. While the configuration is in the editor you can use the "File" menu to save it as a JSON file for later use. You can also load a JSON file into the editor by using the "File" menu. Please note that this will overwrite the current configuration in the editor.

> [!CAUTION]
> The exported JSON file contains sensitive information like the network password in plain text. Please handle it with care.



## ipc_sniff

This is a GUI tool for sniffing IPC outgoing and incoming IPC messages via the USB serial port. It uses eBPF to inject kprobes into the Linux kernel and sniff the communication. As such it runs only on Linux.

![IPC sniffer screenshot ](./docs/ipc_sniff_screenshot.png)

### Installation

The IPC sniffer needs some dependencies for the GUI, which are defined in `pyprojec.toml` as an optional group. To install them run:

```sh
$ poetry install --with=ipc_sniff
# or 
$ poetry install --with=ipc_sniff --with=provisioning_tool
```

Additionally ipc_sniff requires the [BPF Compiler Collection (BCC)](https://github.com/iovisor/bcc), which has to be installed system wide. Please consult this guide for installation instructions relevant for your Linux distribution: https://github.com/iovisor/bcc/blob/master/INSTALL.md

### Usage

To run the IPC sniffer invoke the following command:

```sh
$ poetry run sudo python3 vt_ipc/ipc_sniff/ipc_sniff.py
```

The sniffer requires root privileges to run, as it uses eBPF to inject kprobes into the kernel.

Then a window with serial ports available on your PC will appear. Select the port you want to sniff and press the "Sniff" button next to it.

Then run the application which talks with the device. It can be the QT Tablet application, provisioning tool or any other python tool which talks with the device. Since the sniffer is passive, it will not interfere with the communication. You can even start it while the communication is already ongoing.

Then use the menu bar "Window" menu to open relevant windows for viewing the messages. The "Message analyzer" is the most high level, and probably most useful tool (see screenshot). It shows the transmitted and received messages in a table, and allows for selecting one message to see it's contents. Selecting works best with autoscroll disabled. 


## Factory API Certificate Provisioning

This repository contains tools for flashing factory API certificates (and a default API URL) over IPC via USB. They can be used as a standalone CLI script or Python library.

From the view of the consumer of these tools the process has two stages:

### 1. Issuing a certificate

In this stage a RSA private key is generated on-device and a CSR (Certificate Signing Request) is submitted to a CA. After the CA issues a certificate it is saved in the ESP chip's flash along with the private key and default API url.

> [!IMPORTANT]
> Before provisioning the factory certificate the ID chips must have the serial number of the device, because the serial number is used as the certificate subject.


The provisioning can use two backends for issuing the factory certificate:

#### Option A: [Hashicorp vault](https://www.vaultproject.io/)

With this backend the Vault REST API is used to submit a CSR and get a signed certificate. To work it requires the following `.json` config:

```json
{
  "address": "https://<VAULT INSTANCE URL>",
  "token": "<VALUT TOKEN>",
  "namespace": "admin",
  "role_name": "device-issuer",
  "mount_point": "pki_intermediate_vt_device_ca"
}
```
The `namespace`, `role_name`, `mount_point` shall be adjusted accordingly to how the Vault instance is set up. The file should be considered a secret and be handled with care.

> [!IMPORTANT]
> It is vital that the Hashicorp Vault PKI engine is configured correctly:
> - The root and intermediate CA certs should have a sufficiently long expiration date (as long as the devices will be expected to live)
> - It should set the key usage extension in the issued device certificates to: `Digital Signature, Key Encipherment, Key Agreement`
>
> Flashing certificates which do not adhere to these criteria might prevent the devices from talking to the API. And if the private key is locked via the eFuses it might be impossible to replace them.

CLI certificate provisioning with Hashicorp Vault:


> [!TIP]
> The provisioning can take about a minute as the ESP chip has to generate a 3072-bit RSA key.


```bash
poetry run factory-cert-provisioning provision-device --vault-config vault_config.json --api-url <default API server url>
```

`<default API server url>` could be the url where the production instance of VitalOne lives.

Programmatic certificate provisioning with Hashicorp Vault:

```py
from vt_ipc.cert_provisioning.provision import provision_device
from vt_ipc.cert_provisioning.vault_certificate_signer_backend import (
    VaultCertificateSignerBackend,
    VaultConfig,
)

backend = VaultCertificateSignerBackend(
  VaultConfig.load_from_file("path to vault_config.json")
)

provision_device(ipc_transport, backend, api_url="<default API server url>")
```


#### Option B: Using a local CA cert & key

During development a local CA cert & key can be used instead of Hashicorp Vault. For that please see the source of the CLI script [vt_ipc/scripts/factory_cert_provisioning.py](./vt_ipc/scripts/factory_cert_provisioning.py).

### 2. Burning eFuses to prevent readout of the private key.

> [!NOTE]
> This applies to H10 units only (M10 has an ESP32 chip with no DS peripheral, so the key is stored as-is in the NVS). 

> [!CAUTION]
> This burns the efuse that allows software readout of the HMAC key which encrypts the private key. THIS PROCEDURE IS IRREVERSIBLE.
> After that the factory certificate and key of the device cannot be changed.
>
> The firmware does some sanity checks on the keypair before burning the eFuse, but caution is advised. Please check that the device<->API communication works with the previously flashed credentials before locking it down.

The purpose of this step is to prevent somebody with physical access (or even code execution on the ESP chip) from extracting the factory-provisioned private key and impersonating a Guardian device.

CLI usage:

```bash
poetry run factory-cert-provisioning lock-device-hmac-key
```

Programmatic usage:

```python
lock_device_hmac_key(ipc_transport)
```

This function will throw an error if it fails.