Skip to content

Commit

Permalink
Merge pull request #18 from itzmeanjan/add-ct-tests
Browse files Browse the repository at this point in the history
Add `dudect` -based Timing Leakage Detection Tests
  • Loading branch information
itzmeanjan authored Jan 31, 2024
2 parents 0bc6262 + 4bf4b2c commit b32bd0c
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 3 deletions.
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;
}

0 comments on commit b32bd0c

Please sign in to comment.