Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .clangd
Empty file.
3 changes: 0 additions & 3 deletions .gitmodules

This file was deleted.

2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(Pinata VERSION 3.2 LANGUAGES C ASM)
project(Pinata VERSION 4.0 LANGUAGES C ASM)

if(NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "")
message(STATUS "Setting CMAKE_BUILD_TYPE to MinSizeRel")
Expand Down
30 changes: 25 additions & 5 deletions PinataTests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
cmake_minimum_required(VERSION 3.16)
project(PinataTests C CXX)

if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.27)
cmake_policy(SET CMP0144 NEW)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.30)
cmake_policy(SET CMP0167 NEW)
endif()
endif()

project(PinataTests VERSION 4.0 LANGUAGES C CXX)

find_package(OpenSSL REQUIRED)
find_package(GTest REQUIRED)
find_package(Boost REQUIRED)

set(DILITHIUM PQClean/crypto_sign/dilithium3/clean)
set(KYBER PQClean/crypto_kem/kyber512/clean)
set(COMMON PQClean/common)
include(FetchContent)
FetchContent_Declare(
pqm4
GIT_REPOSITORY https://github.com/mupq/pqm4.git
# Keep this git hash the same as the one in src/CMakeLists.txt !
GIT_TAG a24bb4b662016968c19f5e6a0719c9ad530f0286
)
FetchContent_MakeAvailable(pqm4)

# We can reuse the PQClean sources checked out by PQM4.
set(DILITHIUM "${pqm4_SOURCE_DIR}/mupq/pqclean/crypto_sign/ml-dsa-65/clean")
set(KYBER "${pqm4_SOURCE_DIR}/mupq/pqclean/crypto_kem/ml-kem-512/clean")
set(COMMON "${pqm4_SOURCE_DIR}/mupq/pqclean/common")

add_executable(PinataTests
main.cpp
Expand Down Expand Up @@ -39,5 +57,7 @@ add_executable(PinataTests
)

target_compile_features(PinataTests PRIVATE cxx_std_20)
target_include_directories(PinataTests PRIVATE PQClean/common)
target_include_directories(PinataTests PRIVATE "${pqm4_SOURCE_DIR}/mupq/pqclean/common")
set_source_files_properties(PqcFirmware.cpp PROPERTIES INCLUDE_DIRECTORIES "${pqm4_SOURCE_DIR}/mupq/pqclean")
target_link_libraries(PinataTests PRIVATE Boost::boost OpenSSL::Crypto GTest::GTest)

1 change: 0 additions & 1 deletion PinataTests/PQClean
Submodule PQClean deleted from 4f86c3
89 changes: 45 additions & 44 deletions PinataTests/PqcFirmware.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@
#include <openssl/rand.h>

extern "C" {
#include "PQClean/crypto_kem/kyber512/clean/api.h"
#include "PQClean/crypto_sign/dilithium3/clean/api.h"
#include "crypto_kem/ml-kem-512/clean/api.h"
#include "crypto_sign/ml-dsa-65/clean/api.h"
}

#define DILITHIUM_PUBLIC_KEY_SIZE 1952
#define DILITHIUM_PRIVATE_KEY_SIZE 4016
#define DILITHIUM_SIGNATURE_SIZE 3293
#define DILITHIUM_MESSAGE_SIZE 16
#define DILITHIUM_SIGNED_MESSAGE_SIZE (DILITHIUM_SIGNATURE_SIZE + DILITHIUM_MESSAGE_SIZE)
#define MLDSA_PUBLIC_KEY_SIZE 1952
#define MLDSA_PRIVATE_KEY_SIZE 4032
#define MLDSA_SIGNATURE_SIZE 3309
#define MLDSA_MESSAGE_SIZE 16
#define MLDSA_N 256
#define MLDSA_SIGNED_MESSAGE_SIZE (MLDSA_SIGNATURE_SIZE + MLDSA_MESSAGE_SIZE)

#define KYBER512_PUBLIC_KEY_SIZE 800
#define KYBER512_PRIVATE_KEY_SIZE 1632
#define KYBER512_SHARED_SECRET_SIZE 32
#define KYBER512_CIPHERTEXT_SIZE 768
#define MLKEM_PUBLIC_KEY_SIZE 800
#define MLKEM_PRIVATE_KEY_SIZE 1632
#define MLKEM_SHARED_SECRET_SIZE 32
#define MLKEM_CIPHERTEXT_SIZE 768

#if DILITHIUM_PUBLIC_KEY_SIZE != PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_PUBLICKEYBYTES
#error invalid public key size, update me!
Expand All @@ -33,13 +34,13 @@ extern "C" {
#define DILITHIUM_MODE MODE
#endif

#if KYBER512_PUBLIC_KEY_SIZE != PQCLEAN_KYBER512_CLEAN_CRYPTO_PUBLICKEYBYTES
#if MLKEM_PUBLIC_KEY_SIZE != PQCLEAN_MLKEM512_CLEAN_CRYPTO_PUBLICKEYBYTES
#error invalid public key size, update me!
#endif
#if KYBER512_PRIVATE_KEY_SIZE != PQCLEAN_KYBER512_CLEAN_CRYPTO_SECRETKEYBYTES
#if MLKEM_PRIVATE_KEY_SIZE != PQCLEAN_MLKEM512_CLEAN_CRYPTO_SECRETKEYBYTES
#error invalid secret key size, update me!
#endif
#if KYBER512_SHARED_SECRET_SIZE != PQCLEAN_KYBER512_CLEAN_CRYPTO_BYTES
#if MLKEM_SHARED_SECRET_SIZE != PQCLEAN_MLKEM512_CLEAN_CRYPTO_BYTES
#error invalid secret size, update me!
#endif

Expand All @@ -52,11 +53,11 @@ class PqcFirmware : public TestBase {
};

TEST_F(PqcFirmware, DilithiumLevel3) {
std::array<unsigned char, DILITHIUM_PUBLIC_KEY_SIZE> publicKey;
std::array<unsigned char, DILITHIUM_PRIVATE_KEY_SIZE> privateKey;
std::array<unsigned char, DILITHIUM_MESSAGE_SIZE> message;
std::array<unsigned char, PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_BYTES + DILITHIUM_MESSAGE_SIZE> pinataSignedMessage;
std::array<unsigned char, PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_BYTES + DILITHIUM_MESSAGE_SIZE> referenceSignedMessage;
std::array<unsigned char, MLDSA_PUBLIC_KEY_SIZE> publicKey;
std::array<unsigned char, MLDSA_PRIVATE_KEY_SIZE> privateKey;
std::array<unsigned char, MLDSA_MESSAGE_SIZE> message;
std::array<unsigned char, PQCLEAN_MLDSA65_CLEAN_CRYPTO_BYTES + MLDSA_MESSAGE_SIZE> pinataSignedMessage;
std::array<unsigned char, PQCLEAN_MLDSA65_CLEAN_CRYPTO_BYTES + MLDSA_MESSAGE_SIZE> referenceSignedMessage;

// Ensure the mode is the same
std::cerr << "asserting security level\n";
Expand All @@ -65,11 +66,11 @@ TEST_F(PqcFirmware, DilithiumLevel3) {
// Ensure public and private key sizes match
std::cerr << "checking key sizes\n";
const auto [pinataPublicKeySize, pinataPrivateKeySize] = mClient.dilithiumGetKeySizes();
ASSERT_EQ(pinataPublicKeySize, PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_PUBLICKEYBYTES);
ASSERT_EQ(pinataPrivateKeySize, PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_SECRETKEYBYTES);
ASSERT_EQ(pinataPublicKeySize, PQCLEAN_MLDSA65_CLEAN_CRYPTO_PUBLICKEYBYTES);
ASSERT_EQ(pinataPrivateKeySize, PQCLEAN_MLDSA65_CLEAN_CRYPTO_SECRETKEYBYTES);

// Generate a public/private key pair with the reference X86 implementation
PQCLEAN_DILITHIUM3_CLEAN_crypto_sign_keypair(publicKey.data(), privateKey.data());
PQCLEAN_MLDSA65_CLEAN_crypto_sign_keypair(publicKey.data(), privateKey.data());

// Tell the pinata to use this public/private key pair for signing with Dilithium3
std::cerr << "setup public/private key pair\n";
Expand All @@ -82,24 +83,24 @@ TEST_F(PqcFirmware, DilithiumLevel3) {
// Sign the fuzzed message on pinata
std::cerr << "sign message\n";
mClient.dilithiumSign(message.data(), message.size(), pinataSignedMessage.data(),
PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_BYTES);
PQCLEAN_MLDSA65_CLEAN_CRYPTO_BYTES);

// Concatenate the signature and the fuzzed message together to obtain a "signed message"
ASSERT_EQ(pinataSignedMessage.size(), PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_BYTES + message.size());
std::copy(message.begin(), message.end(), pinataSignedMessage.data() + PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_BYTES);
ASSERT_EQ(pinataSignedMessage.size(), PQCLEAN_MLDSA65_CLEAN_CRYPTO_BYTES + message.size());
std::copy(message.begin(), message.end(), pinataSignedMessage.data() + PQCLEAN_MLDSA65_CLEAN_CRYPTO_BYTES);

// The message should be at the end of the signed message buffer
ASSERT_EQ(std::memcmp(pinataSignedMessage.data() + PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_BYTES, message.begin(), 16), 0);
ASSERT_EQ(std::memcmp(pinataSignedMessage.data() + PQCLEAN_MLDSA65_CLEAN_CRYPTO_BYTES, message.begin(), 16), 0);

// Sign the fuzzed message with the X86 reference implementation.
// The reference implementation doesn't use randomized signatures.
unsigned long messageLength = static_cast<unsigned long>(pinataSignedMessage.size());
PQCLEAN_DILITHIUM3_CLEAN_crypto_sign(referenceSignedMessage.data(), &messageLength, message.data(), message.size(),
PQCLEAN_MLDSA65_CLEAN_crypto_sign(referenceSignedMessage.data(), &messageLength, message.data(), message.size(),
privateKey.data());
ASSERT_EQ(messageLength, referenceSignedMessage.size());

// Pinata sign --> Reference verify
ASSERT_EQ(PQCLEAN_DILITHIUM3_CLEAN_crypto_sign_open(pinataSignedMessage.data(), &messageLength,
ASSERT_EQ(PQCLEAN_MLDSA65_CLEAN_crypto_sign_open(pinataSignedMessage.data(), &messageLength,
pinataSignedMessage.data(), pinataSignedMessage.size(),
publicKey.data()),
0);
Expand All @@ -110,25 +111,25 @@ TEST_F(PqcFirmware, DilithiumLevel3) {
}

TEST_F(PqcFirmware, Kyber512) {
std::array<unsigned char, KYBER512_PUBLIC_KEY_SIZE> publicKey;
std::array<unsigned char, KYBER512_PRIVATE_KEY_SIZE> privateKey;
std::array<unsigned char, PQCLEAN_KYBER512_CLEAN_CRYPTO_BYTES> ssPinata;
std::array<unsigned char, PQCLEAN_KYBER512_CLEAN_CRYPTO_BYTES> ssRef;
std::array<unsigned char, PQCLEAN_KYBER512_CLEAN_CRYPTO_BYTES> ssPinataGenerateRefDecode;
std::array<unsigned char, PQCLEAN_KYBER512_CLEAN_CRYPTO_BYTES> ssRefGeneratePinataDecode;
std::array<unsigned char, PQCLEAN_KYBER512_CLEAN_CRYPTO_BYTES> ssRefGenerateRefDecode;
std::array<unsigned char, PQCLEAN_KYBER512_CLEAN_CRYPTO_BYTES> ssPinataGeneratePinataDecode;
std::array<unsigned char, PQCLEAN_KYBER512_CLEAN_CRYPTO_CIPHERTEXTBYTES> ctPinata;
std::array<unsigned char, PQCLEAN_KYBER512_CLEAN_CRYPTO_CIPHERTEXTBYTES> ctRef;
std::array<unsigned char, MLKEM_PUBLIC_KEY_SIZE> publicKey;
std::array<unsigned char, MLKEM_PRIVATE_KEY_SIZE> privateKey;
std::array<unsigned char, PQCLEAN_MLKEM512_CLEAN_CRYPTO_BYTES> ssPinata;
std::array<unsigned char, PQCLEAN_MLKEM512_CLEAN_CRYPTO_BYTES> ssRef;
std::array<unsigned char, PQCLEAN_MLKEM512_CLEAN_CRYPTO_BYTES> ssPinataGenerateRefDecode;
std::array<unsigned char, PQCLEAN_MLKEM512_CLEAN_CRYPTO_BYTES> ssRefGeneratePinataDecode;
std::array<unsigned char, PQCLEAN_MLKEM512_CLEAN_CRYPTO_BYTES> ssRefGenerateRefDecode;
std::array<unsigned char, PQCLEAN_MLKEM512_CLEAN_CRYPTO_BYTES> ssPinataGeneratePinataDecode;
std::array<unsigned char, PQCLEAN_MLKEM512_CLEAN_CRYPTO_CIPHERTEXTBYTES> ctPinata;
std::array<unsigned char, PQCLEAN_MLKEM512_CLEAN_CRYPTO_CIPHERTEXTBYTES> ctRef;

// Ensure public and private key sizes match
std::cerr << "checking wether key sizes agree\n";
const auto [pinataPublicKeySize, pinataPrivateKeySize] = mClient.kyber512GetKeySizes();
ASSERT_EQ(pinataPublicKeySize, PQCLEAN_KYBER512_CLEAN_CRYPTO_PUBLICKEYBYTES);
ASSERT_EQ(pinataPrivateKeySize, PQCLEAN_KYBER512_CLEAN_CRYPTO_SECRETKEYBYTES);
ASSERT_EQ(pinataPublicKeySize, PQCLEAN_MLKEM512_CLEAN_CRYPTO_PUBLICKEYBYTES);
ASSERT_EQ(pinataPrivateKeySize, PQCLEAN_MLKEM512_CLEAN_CRYPTO_SECRETKEYBYTES);

// Generate a public/private key pair with the reference X86 implementation
PQCLEAN_KYBER512_CLEAN_crypto_kem_keypair(publicKey.data(), privateKey.data());
PQCLEAN_MLKEM512_CLEAN_crypto_kem_keypair(publicKey.data(), privateKey.data());

// Tell the pinata to use this public/private key pair for encrypting shared secrets with kyber512.
std::cerr << "setting public private key pair\n";
Expand All @@ -145,13 +146,13 @@ TEST_F(PqcFirmware, Kyber512) {
mClient.kyber512Generate(ssPinata.data(), ssPinata.size(), ctPinata.data(), ctPinata.size());

// Generate a shared secret with the reference implementation.
PQCLEAN_KYBER512_CLEAN_crypto_kem_enc(ctRef.data(), ssRef.data(), publicKey.data());
PQCLEAN_MLKEM512_CLEAN_crypto_kem_enc(ctRef.data(), ssRef.data(), publicKey.data());

// Decode the Pinata ciphertext with ref impl
PQCLEAN_KYBER512_CLEAN_crypto_kem_dec(ssPinataGenerateRefDecode.data(), ctPinata.data(), privateKey.data());
PQCLEAN_MLKEM512_CLEAN_crypto_kem_dec(ssPinataGenerateRefDecode.data(), ctPinata.data(), privateKey.data());

// Decode the ref ciphertext with ref impl
PQCLEAN_KYBER512_CLEAN_crypto_kem_dec(ssRefGenerateRefDecode.data(), ctRef.data(), privateKey.data());
PQCLEAN_MLKEM512_CLEAN_crypto_kem_dec(ssRefGenerateRefDecode.data(), ctRef.data(), privateKey.data());

// Decode the Pinata ciphertext with Pinata impl
std::cerr << "decoding shared secret\n";
Expand Down
77 changes: 46 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Pinata board

Pinata is a development board (ARM Cortex-M4F) that has been modified and programmed in order to be a training target
for Side Channel Analysis (SCA) and Fault Injection (FI) attacks.
for Side Channel Analysis (SCA) and Fault Injection (FI) attacks. It is based on the STM32F4Discovery board.

## Features

Expand Down Expand Up @@ -77,18 +77,16 @@ in the manual).
#### Lattice-based
| | | SW | HW |
|--------------------|--------|----------|----|
| CRYSTALS-Dilithium | | | - |
| | LEVEL2 | - | - |
| | LEVEL3 | SIG, VER | - |
| | LEVEL5 | - | - |
| CRYSTALS-Kyber | | | |
| ML-DSA FIPS 204 | | | - |
| | 44 | - | - |
| | 65 | SIG, VER | - |
| | 87 | - | - |
| MKL-KEM FIPS 203 | | | |
| | 512 | ENC, DEC | - |
| | 768 | - | - |
| | 1024 | - | - |
| | 512-90s| - | - |
| | 768-90s| - | - |
| |1024-90s| - | - |

Note: ML-DSA and ML-KEM are implemented in terms of the [PQM4 library for Cortex-M4 processors](https://github.com/mupq/pqm4.git). The exact git commit hash that is used can be found in the src/CMakeLists.txt file. The library is downloaded into the $BUILD/\_deps/pqm4-src folder.

### Hash functions

Expand All @@ -113,65 +111,82 @@ Hardware only

## Building Pinata Firmware

You can build pinata firmware either using wsl or on a native linux machine. The description bellow is based on an
UBUNTU 22.04 machine. These steps will also work for wsl, but to get access to the Pinata board in wsl, see the
troubleshooting steps about Windows and wsl.
You can build Pinata firmware either using WSL or on a native linux machine. The description below is based on an UBUNTU 22.04 machine. These steps will also work for WSL, but to get access to the Pinata board in WSL, see the troubleshooting steps about Windows and WSL.

### Requirements

For cross - compiling the STM32F4Discovery board, you will need a `gcc-arm-none-eabi` toolchain and `cmake`
For cross-compiling, and flashing, the STM32F4Discovery board, install the following packages:
```sh
sudo apt-get install gcc-arm-none-eabi cmake
```

For flashing the STM32F4Discovery board, you will need the dfu-util toolkit:
```sh
sudo apt-get install dfu-util
sudo apt-get install gcc-arm-none-eabi cmake dfu-util
```

### Cross-compiling the firmware

For cross-compiling pinata:
#### Configure

Run the following command to configure the project for the gcc-arm-non-eabi compiler toolchain:

```sh
cmake -DCMAKE_TOOLCHAIN_FILE=gcc-arm-none-eabi.toolchain.cmake -S. -Bbuild && cmake --build build
cmake -DCMAKE_TOOLCHAIN_FILE=gcc-arm-none-eabi.toolchain.cmake -S . -B build
```

This will compile all Pinata variations. Output binaries can be found in the `build` folder.
This will create a ./build folder where you run your Makefile targets. You may customize the path to your compiler toolchain by definining a `PREFIX` variable on the command-line when invoking cmake. See the `gcc-arm-none-eabi.toolchain.cmake` toolchain file for details. (For regular Ubuntu/WSL installations, you don't need to modify the `PREFIX` variable).

#### Build

To build everything, just run `make` inside the configured ./build folder.

This will compile all Pinata variations, which are currently "classic", "hw", and "pqc". Output binaries can be found in the `./build/src` folder.

* The "classic" variant contains non-pqc software ciphers.
* The "hw" variant contains non-pqc software ciphers, as well as _hardware-accelerated_ ciphers.
* The "pqc" variant contains ML-DSA FIPS 204 and ML-KEM FIPS 203 software implementations.

Example of compiling a particular firmware:

```sh
cmake --build build --target classic_bin
```
In general, there is a `help` Makefile target defined that you may invoke to view the possible targets to build.

### Flashing the firmware
### Flashing the firmware (Linux-based)

Add a udev rule for the Pinata:

```sh
sudo mkdir -p /etc/udev/rules.d && echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", MODE="0666", GROUP="plugdev"' | sudo tee /etc/udev/rules.d/69-pinata.rules
```

Add user to the `plugdev` group:
Add your user to the `plugdev` group:

```sh
sudo usermod -a -G plugdev $USER
```

Check if the physical Pinata is connected to the build machine using the micro USB port on the Pinata.

Run the following command for flashing classic firmware
Each firmware variant (classic, hw, pqc) has an associated "flash target" that allows one to flash the device while making sure the firmware is up-to-date with the source code. These special targets are named:

* classic_flash
* hw_flash
* pqc_flash

For example, run the following command for flashing the classic firmware onto the connected device:

```sh
cmake --build build --target classic_flash
make classic_flash
```

Note that this command also makes sure the firmware binary is up-to-date, so for quick iteration loops you can just always run this after editing source code.
This is assuming you configured with the _Unix Makefiles_ generator.

## Testing

For more information on testing Pinata functionality, see [PinataTests/README.md](PinataTests/README.md).
We maintain some integration tests for ensuring the ciphers on the device match reference implementations in the real world. For more information on testing Pinata functionality, see [PinataTests/README.md](PinataTests/README.md).

## Usage

The Pinata firmware works in a "request-response" manner where it waits for a command to appear via UART, optionally with arguments, then processes the command, and then optionally sends back a response.

The available commands are described in the src/main.h file. In there, each `#define` line that starts with `CMD_` is a possible request. Each command is 1 byte, and the argument list for the command depends on the particular command. The arguments for the command are described in comments above the `#define` line.

For the purposes of side-channel analysis, you are supposed to measure the voltage of the chip while the Pinata firmware is running a cryptographic operation. This has been made easy for you to do, because the interesting operations are wrapped in macro blocks named `BEGIN_INTERESTING_STUFF` and `END_INTERESTING_STUFF`. These macros will set GPIO Pin 2 to high and low, respectively. This allows you to trigger an oscilloscope on this GPIO pin and you'll know exactly where the interesting operation happens.

## Troubleshooting

Expand Down
Loading
Loading