From d8fda3b6da78c5a62c77fe5d75b9a033ef94e882 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 27 Aug 2021 17:46:25 +0100 Subject: [PATCH] testsc: add side-channel test of probabilistic prime gen. Now that I've removed side-channel leakage from both prime candidate generation (via mp_unsafe_mod_integer) and Miller-Rabin, the probabilistic prime generation system in this code base is now able to get through testsc without it detecting any source of cache or timing side channels. So you should be able to generate an RSA key (in which the primes themselves must be secret) in a more hostile environment than you could previously be confident of. This is a bit counterintuitive, because _obviously_ random prime generation takes a variable amount of time, because it has to keep retrying until an attempt succeeds! But that's OK as long as the attempts are completely independent, because then any timing or cache information leaked by a _failed_ attempt will only tell an attacker about the numbers used in the failed attempt, and those numbers have been thrown away, so it doesn't matter who knows them. It's only important that the _successful_ attempt, from generating the random candidate through to completing its verification as (probably) prime, should be side-channel clean, because that's the attempt whose data is actually going to be turned into a private key that needs to be kept secret. (In particular, this means you have to avoid the old-fashioned strategy of generating successive prime candidates by incrementing a starting value until you find something not divisible by any small prime, because the number of iterations of that method would be a timing leak. Happily, we stopped doing that last year, in commit 08a3547bc54051e: now every candidate integer is generated independently, and if one fails the initial checks, we throw it away and start completely from scratch with a fresh random value.) So the test harness works by repeatedly running the prime generator in one-shot mode until an attempt succeeds, and then resetting the random-number stream to where it was just before the successful attempt. Then we generate the same prime number again, this time with the sclog mechanism turned on - and then, we compare it against the version we previously generated with the same random numbers, to make sure they're the same. This checks that the attempts really _are_ independent, in the sense that the prime generator is a pure function of its random input stream, and doesn't depend on state left over from previous attempts. --- testsc.c | 91 +++++++++++++++++++++++++++++++++++++++++++++ unix/CMakeLists.txt | 2 +- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/testsc.c b/testsc.c index a3e8b5d0..3ac88233 100644 --- a/testsc.c +++ b/testsc.c @@ -77,6 +77,7 @@ #include "defs.h" #include "putty.h" #include "ssh.h" +#include "sshkeygen.h" #include "misc.h" #include "mpint.h" #include "crypto/ecc.h" @@ -130,6 +131,31 @@ void random_read(void *vbuf, size_t size) } } +struct random_state { + const char *seedstr; + uint64_t counter; + size_t limit; + uint8_t buf[MAX_HASH_LEN]; +}; + +static struct random_state random_get_state(void) +{ + struct random_state st; + st.seedstr = random_seedstr; + st.counter = random_counter; + st.limit = random_buf_limit; + memcpy(st.buf, random_buf, sizeof(st.buf)); + return st; +} + +static void random_set_state(struct random_state st) +{ + random_seedstr = st.seedstr; + random_counter = st.counter; + random_buf_limit = st.limit; + memcpy(random_buf, st.buf, sizeof(random_buf)); +} + /* * Macro that defines a function, and also a volatile function pointer * pointing to it. Callers indirect through the function pointer @@ -177,6 +203,22 @@ void log_end(void) log_to_file(NULL); sfree(last_filename); } +void log_discard(void) +{ + /* + * Discard the most recently generated log file, and rewind the + * index so that its name will be reused by the next attempt. + * + * Used in tests of prime generation, in which only the + * _successful_ attempts need to be side-channel safe: it doesn't + * matter if a failed attempt leaks its data through early + * termination of a checking loop, because all the data it leaked + * will be thrown away anyway. + */ + char *prev_filename = log_filename(test_basename, --test_index); + remove(prev_filename); + sfree(prev_filename); +} static bool test_skipped = false; @@ -364,6 +406,7 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) MACS(MAC_TESTLIST, X) \ HASHES(HASH_TESTLIST, X) \ X(argon2) \ + X(primegen_probabilistic) \ /* end of list */ static void test_mp_get_nbits(void) @@ -1476,6 +1519,54 @@ static void test_argon2(void) strbuf_free(outdata); } +static void test_primegen(const PrimeGenerationPolicy *policy) +{ + static ProgressReceiver null_progress = { .vt = &null_progress_vt }; + + PrimeGenerationContext *pgc = primegen_new_context(policy); + + init_smallprimes(); + mp_int *pcopy = mp_new(128); + + for (size_t i = 0; i < looplimit(2); i++) { + while (true) { + struct random_state st = random_get_state(); + + PrimeCandidateSource *pcs = pcs_new(128); + pcs_set_oneshot(pcs); + pcs_ready(pcs); + mp_int *p = primegen_generate(pgc, pcs, &null_progress); + + if (p) { + mp_copy_into(pcopy, p); + sfree(p); + + random_set_state(st); + + log_start(); + PrimeCandidateSource *pcs = pcs_new(128); + pcs_set_oneshot(pcs); + pcs_ready(pcs); + mp_int *q = primegen_generate(pgc, pcs, &null_progress); + log_end(); + + assert(q); + assert(mp_cmp_eq(pcopy, q)); + mp_free(q); + break; + } + } + } + + mp_free(pcopy); + primegen_free_context(pgc); +} + +static void test_primegen_probabilistic(void) +{ + test_primegen(&primegen_probabilistic); +} + static const struct test tests[] = { #define STRUCT_TEST(X) { #X, test_##X }, TESTLIST(STRUCT_TEST) diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 7ce5e669..37ea4337 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -99,7 +99,7 @@ target_link_libraries(cgtest add_executable(testsc ${CMAKE_SOURCE_DIR}/testsc.c) -target_link_libraries(testsc crypto utils) +target_link_libraries(testsc keygen crypto utils) add_executable(testzlib ${CMAKE_SOURCE_DIR}/testzlib.c