Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dudect -based Timing Leakage Detection Tests #18

Merged
merged 5 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "gtest-parallel"]
path = gtest-parallel
url = https://github.com/google/gtest-parallel.git
[submodule "dudect"]
path = dudect
url = https://github.com/oreparaz/dudect.git
19 changes: 18 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@ ASAN_FLAGS = -g -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsaniti
UBSAN_FLAGS = -g -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=undefined # From https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html

SHA3_INC_DIR = ./sha3/include
DUDECT_INC_DIR = ./dudect/src
I_FLAGS = -I ./include
DEP_IFLAGS = -I $(SHA3_INC_DIR)
DUDECT_DEP_IFLAGS = $(DEP_IFLAGS) -I $(DUDECT_INC_DIR)

SRC_DIR = include
SPHINCS+_SOURCES := $(wildcard $(SRC_DIR)/*.hpp)
BUILD_DIR = build
ASAN_BUILD_DIR = $(BUILD_DIR)/asan
UBSAN_BUILD_DIR = $(BUILD_DIR)/ubsan
DUDECT_BUILD_DIR = $(BUILD_DIR)/dudect

TEST_DIR = tests
DUDECT_TEST_DIR = $(TEST_DIR)/dudect
TEST_SOURCES := $(wildcard $(TEST_DIR)/*.cpp)
TEST_OBJECTS := $(addprefix $(BUILD_DIR)/, $(notdir $(patsubst %.cpp,%.o,$(TEST_SOURCES))))
ASAN_TEST_OBJECTS := $(addprefix $(ASAN_BUILD_DIR)/, $(notdir $(patsubst %.cpp,%.o,$(TEST_SOURCES))))
UBSAN_TEST_OBJECTS := $(addprefix $(UBSAN_BUILD_DIR)/, $(notdir $(patsubst %.cpp,%.o,$(TEST_SOURCES))))
DUDECT_TEST_SOURCES := $(wildcard $(DUDECT_TEST_DIR)/*.cpp)
DUDECT_TEST_BINARIES := $(addprefix $(DUDECT_BUILD_DIR)/, $(notdir $(patsubst %.cpp,%.out,$(DUDECT_TEST_SOURCES))))
TEST_LINK_FLAGS = -lgtest -lgtest_main
TEST_BINARY = $(BUILD_DIR)/test.out
ASAN_TEST_BINARY = $(ASAN_BUILD_DIR)/test.out
Expand Down Expand Up @@ -48,12 +54,18 @@ $(ASAN_BUILD_DIR): $(BUILD_DIR)
$(UBSAN_BUILD_DIR): $(BUILD_DIR)
mkdir -p $@

$(DUDECT_BUILD_DIR): $(BUILD_DIR)
mkdir -p $@

$(SHA3_INC_DIR):
git submodule update --init

$(GTEST_PARALLEL): $(SHA3_INC_DIR)
git submodule update --init

$(DUDECT_INC_DIR): $(GTEST_PARALLEL)
git submodule update --init

$(BUILD_DIR)/%.o: $(TEST_DIR)/%.cpp $(BUILD_DIR) $(SHA3_INC_DIR)
$(CXX) $(CXX_FLAGS) $(WARN_FLAGS) $(OPT_FLAGS) $(I_FLAGS) $(DEP_IFLAGS) -c $< -o $@

Expand All @@ -72,6 +84,9 @@ $(ASAN_TEST_BINARY): $(ASAN_TEST_OBJECTS)
$(UBSAN_TEST_BINARY): $(UBSAN_TEST_OBJECTS)
$(CXX) $(UBSAN_FLAGS) $^ $(TEST_LINK_FLAGS) -o $@

$(DUDECT_BUILD_DIR)/%.out: $(DUDECT_TEST_DIR)/%.cpp $(DUDECT_BUILD_DIR) $(SHA3_INC_DIR) $(DUDECT_INC_DIR)
$(CXX) $(CXX_FLAGS) $(WARN_FLAGS) $(OPT_FLAGS) $(I_FLAGS) $(DUDECT_DEP_IFLAGS) -lm $(LINK_FLAGS) $< -o $@

test: $(TEST_BINARY) $(GTEST_PARALLEL)
$(GTEST_PARALLEL) $< --print_test_times

Expand All @@ -81,6 +96,8 @@ asan_test: $(ASAN_TEST_BINARY) $(GTEST_PARALLEL)
ubsan_test: $(UBSAN_TEST_BINARY) $(GTEST_PARALLEL)
$(GTEST_PARALLEL) $< --print_test_times

dudect_test_build: $(DUDECT_TEST_BINARIES)

$(BUILD_DIR)/%.o: $(BENCHMARK_DIR)/%.cpp $(BUILD_DIR) $(SHA3_INC_DIR)
$(CXX) $(CXX_FLAGS) $(WARN_FLAGS) $(OPT_FLAGS) $(I_FLAGS) $(DEP_IFLAGS) -c $< -o $@

Expand All @@ -103,5 +120,5 @@ perf: $(PERF_BINARY)
clean:
rm -rf $(BUILD_DIR)

format: $(SPHINCS+_SOURCES) $(TEST_SOURCES) $(BENCHMARK_SOURCES) $(BENCHMARK_HEADERS)
format: $(SPHINCS+_SOURCES) $(TEST_SOURCES) $(DUDECT_TEST_SOURCES) $(BENCHMARK_SOURCES) $(BENCHMARK_HEADERS)
clang-format -i $^
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
> [!CAUTION]
> This Sphincs+ implementation is conformant with the Sphincs+ [specification](https://sphincs.org/data/sphincs+-r3.1-specification.pdf) and I also try to make it constant-time but be informed that it is not yet audited. **If you consider using it in production, be careful !**
> This Sphincs+ implementation is conformant with Sphincs+ specification @ https://sphincs.org/data/sphincs+-r3.1-specification.pdf. I also try to make it timing leakage free, using `dudect` (see https://github.com/oreparaz/dudect) -based tests, but be informed that this implementation is not yet audited. *If you consider using it in production, be careful !*

# sphincs-plus
SPHINCS+: Stateless Hash-based Digital Signature Algorithm
Expand Down Expand Up @@ -73,7 +73,9 @@ For ensuring that SPHINCS+ implementation is functionally correct and compliant
> This implementation of SPHINCS+ specification is **tested** to be compatible and conformant with r3.1 of the specification. That's ensured by generating known answer tests ( KATs ) following https://gist.github.com/itzmeanjan/d483872509b8a1a7c4d6614ec9d43e6c and testing this implementation using those test vectors.

```bash
make -j
make -j # Run tests without any sort of sanitizers
make asan_test -j # Run tests with AddressSanitizer enabled
make ubsan_test -j # Run tests with UndefinedBehaviourSanitizer enabled
```

```bash
Expand Down Expand Up @@ -108,6 +110,45 @@ PASSED TESTS (27/27):
592956 ms: build/test.out SphincsPlus.SphincsPlus192sRobustKnownAnswerTests
```

You can run timing leakage tests, using `dudect`; execute following

> [!NOTE]
> `dudect` is integrated into this library implementation of Sphincs+ DSA to find any sort of timing leakages. It checks for constant-timeness of most of both `keygen` and `sign` function implementations, for only one variant i.e. **128f-simple**.

```bash
# Can only be built and run on x86_64 machine.
make dudect_test_build -j

# Before running the constant-time tests, it's a good idea to put all CPU cores on "performance" mode.
# You may find the guide @ https://github.com/google/benchmark/blob/main/docs/reducing_variance.md helpful.

# Given Sphincs+ is slow, compared to Dilithium/ Falcon, following tests are required to be run
# for longer, so that we can collect enough execution timing samples.
timeout 2h taskset -c 0 ./build/dudect/test_sphincs+_128f_simple_keygen.out
timeout 2h taskset -c 0 ./build/dudect/test_sphincs+_128f_simple_sign.out
```

> [!TIP]
> `dudect` documentation says if `t` statistic is `< 10`, we're *probably* good, yes **probably**. You may want to read `dudect` documentation @ https://github.com/oreparaz/dudect. Also you might find the original paper @ https://ia.cr/2016/1123 interesting.

```bash
...
meas: 0.69 M, max t: +2.58, max tau: 3.11e-03, (5/tau)^2: 2.58e+06. For the moment, maybe constant time.
meas: 0.70 M, max t: +2.74, max tau: 3.27e-03, (5/tau)^2: 2.34e+06. For the moment, maybe constant time.
meas: 0.71 M, max t: +2.73, max tau: 3.24e-03, (5/tau)^2: 2.38e+06. For the moment, maybe constant time.
meas: 0.72 M, max t: +2.62, max tau: 3.09e-03, (5/tau)^2: 2.61e+06. For the moment, maybe constant time.
meas: 0.73 M, max t: +2.66, max tau: 3.11e-03, (5/tau)^2: 2.58e+06. For the moment, maybe constant time.
meas: 0.74 M, max t: +2.70, max tau: 3.14e-03, (5/tau)^2: 2.53e+06. For the moment, maybe constant time.
meas: 0.75 M, max t: +2.62, max tau: 3.03e-03, (5/tau)^2: 2.72e+06. For the moment, maybe constant time.
meas: 0.76 M, max t: +2.60, max tau: 2.99e-03, (5/tau)^2: 2.80e+06. For the moment, maybe constant time.
meas: 0.77 M, max t: +2.62, max tau: 2.99e-03, (5/tau)^2: 2.80e+06. For the moment, maybe constant time.
meas: 0.78 M, max t: +2.52, max tau: 2.85e-03, (5/tau)^2: 3.07e+06. For the moment, maybe constant time.
meas: 0.79 M, max t: +2.57, max tau: 2.89e-03, (5/tau)^2: 3.00e+06. For the moment, maybe constant time.
meas: 0.80 M, max t: +2.51, max tau: 2.81e-03, (5/tau)^2: 3.18e+06. For the moment, maybe constant time.
meas: 0.81 M, max t: +2.49, max tau: 2.77e-03, (5/tau)^2: 3.25e+06. For the moment, maybe constant time.
meas: 0.82 M, max t: +2.52, max tau: 2.78e-03, (5/tau)^2: 3.23e+06. For the moment, maybe constant time.
```

## Benchmarking

Benchmarking key generation, signing and verification algorithms for various instantiations of SPHINCS+ digital signature scheme can be done, by issuing
Expand Down
1 change: 1 addition & 0 deletions dudect
Submodule dudect added at a18fde
78 changes: 78 additions & 0 deletions tests/dudect/test_sphincs+_128f_simple_keygen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include "sphincs+_128f_simple.hpp"
#include <cstdio>

#define DUDECT_IMPLEMENTATION
#define DUDECT_VISIBLITY_STATIC
#include "dudect.h"

uint8_t
do_one_computation(uint8_t* const data)
{
constexpr size_t doff0 = 0;
constexpr size_t doff1 = doff0 + sphincs_plus_128f_simple::n;
constexpr size_t doff2 = doff1 + sphincs_plus_128f_simple::n;
constexpr size_t doff3 = doff2 + sphincs_plus_128f_simple::n;

auto sk_seed = std::span<const uint8_t, doff1 - doff0>(data + doff0, doff1 - doff0);
auto sk_prf = std::span<const uint8_t, doff2 - doff1>(data + doff1, doff2 - doff1);
auto pk_seed = std::span<const uint8_t, doff3 - doff2>(data + doff2, doff3 - doff2);

std::array<uint8_t, sphincs_plus_128f_simple::SecKeyLen> skey{};
std::array<uint8_t, sphincs_plus_128f_simple::PubKeyLen> pkey{};

uint8_t ret_val = 0;

sphincs_plus_128f_simple::keygen(sk_seed, sk_prf, pk_seed, skey, pkey);
ret_val ^= (skey[0] ^ skey[skey.size() - 1]) ^ (pkey[0] ^ pkey[pkey.size() - 1]);

return ret_val;
}

void
prepare_inputs(dudect_config_t* const c, uint8_t* const input_data, uint8_t* const classes)
{
randombytes(input_data, c->number_measurements * c->chunk_size);

for (size_t i = 0; i < c->number_measurements; i++) {
classes[i] = randombit();
if (classes[i] == 0) {
std::memset(input_data + i * c->chunk_size, 0x00, c->chunk_size);
}
}
}

dudect_state_t
test_sphincs_plus_128f_simple_keygen()
{
constexpr size_t chunk_size = sphincs_plus_128f_simple::n + // Secret Key Seed
sphincs_plus_128f_simple::n + // Secret Key PRF
sphincs_plus_128f_simple::n; // Public Key Seed
constexpr size_t number_measurements = 1e5;

dudect_config_t config = {
chunk_size,
number_measurements,
};
dudect_ctx_t ctx;
dudect_init(&ctx, &config);

dudect_state_t state = DUDECT_NO_LEAKAGE_EVIDENCE_YET;
while (state == DUDECT_NO_LEAKAGE_EVIDENCE_YET) {
state = dudect_main(&ctx);
}

dudect_free(&ctx);

printf("Detected timing leakage in \"%s\", defined in file \"%s\"\n", __func__, __FILE_NAME__);
return state;
}

int
main()
{
if (test_sphincs_plus_128f_simple_keygen() != DUDECT_NO_LEAKAGE_EVIDENCE_YET) {
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}
95 changes: 95 additions & 0 deletions tests/dudect/test_sphincs+_128f_simple_sign.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "prng.hpp"
#include "sphincs+_128f_simple.hpp"
#include <cstdio>

#define DUDECT_IMPLEMENTATION
#define DUDECT_VISIBLITY_STATIC
#include "dudect.h"

constexpr size_t MSG_BYTE_LEN = 64;

uint8_t
do_one_computation(uint8_t* const data)
{
constexpr size_t doff0 = 0;
constexpr size_t doff1 = doff0 + MSG_BYTE_LEN;
constexpr size_t doff2 = doff1 + sphincs_plus_128f_simple::SecKeyLen;
constexpr size_t doff3 = doff2 + sphincs_plus_128f_simple::n;

auto msg = std::span(data + doff0, doff1 - doff0);
auto skey = std::span<const uint8_t, doff2 - doff1>(data + doff1, doff2 - doff1);
auto rand_bytes = std::span<const uint8_t, doff3 - doff2>(data + doff2, doff3 - doff2);

std::array<uint8_t, sphincs_plus_128f_simple::SigLen> sig{};

uint8_t ret_val = 0;

sphincs_plus_128f_simple::sign<true>(msg, skey, rand_bytes, sig);
ret_val ^= sig[0] ^ sig[sig.size() - 1];

return ret_val;
}

void
prepare_inputs(dudect_config_t* const c, uint8_t* const input_data, uint8_t* const classes)
{
randombytes(input_data, c->number_measurements * c->chunk_size);

for (size_t i = 0; i < c->number_measurements; i++) {
classes[i] = randombit();
if (classes[i] == 0) {
const size_t chunk_off = i * c->chunk_size;
const size_t skey_begin = chunk_off + MSG_BYTE_LEN;

std::array<uint8_t, sphincs_plus_128f_simple::n> sk_seed{};
std::array<uint8_t, sphincs_plus_128f_simple::n> sk_prf{};
std::array<uint8_t, sphincs_plus_128f_simple::n> pk_seed{};
auto skey = std::span<uint8_t, sphincs_plus_128f_simple::SecKeyLen>(input_data + skey_begin, sphincs_plus_128f_simple::SecKeyLen);
std::array<uint8_t, sphincs_plus_128f_simple::PubKeyLen> pkey{};

prng::prng_t prng;

prng.read(sk_seed);
prng.read(sk_prf);
prng.read(pk_seed);

sphincs_plus_128f_simple::keygen(sk_seed, sk_prf, pk_seed, skey, pkey);
}
}
}

dudect_state_t
test_sphincs_plus_128f_simple_sign()
{
constexpr size_t chunk_size = MSG_BYTE_LEN + // Message to be signed
sphincs_plus_128f_simple::SecKeyLen + // Secret key used for signing the message
sphincs_plus_128f_simple::n; // Random bytes used for randomized message signing
constexpr size_t number_measurements = 1e4;

dudect_config_t config = {
chunk_size,
number_measurements,
};
dudect_ctx_t ctx;
dudect_init(&ctx, &config);

dudect_state_t state = DUDECT_NO_LEAKAGE_EVIDENCE_YET;
while (state == DUDECT_NO_LEAKAGE_EVIDENCE_YET) {
state = dudect_main(&ctx);
}

dudect_free(&ctx);

printf("Detected timing leakage in \"%s\", defined in file \"%s\"\n", __func__, __FILE_NAME__);
return state;
}

int
main()
{
if (test_sphincs_plus_128f_simple_sign() != DUDECT_NO_LEAKAGE_EVIDENCE_YET) {
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}