mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
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
08a3547bc5
: 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.
This commit is contained in:
parent
1c78d18acb
commit
d8fda3b6da
91
testsc.c
91
testsc.c
@ -77,6 +77,7 @@
|
|||||||
#include "defs.h"
|
#include "defs.h"
|
||||||
#include "putty.h"
|
#include "putty.h"
|
||||||
#include "ssh.h"
|
#include "ssh.h"
|
||||||
|
#include "sshkeygen.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "mpint.h"
|
#include "mpint.h"
|
||||||
#include "crypto/ecc.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
|
* Macro that defines a function, and also a volatile function pointer
|
||||||
* pointing to it. Callers indirect through the function pointer
|
* pointing to it. Callers indirect through the function pointer
|
||||||
@ -177,6 +203,22 @@ void log_end(void)
|
|||||||
log_to_file(NULL);
|
log_to_file(NULL);
|
||||||
sfree(last_filename);
|
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;
|
static bool test_skipped = false;
|
||||||
|
|
||||||
@ -364,6 +406,7 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x))
|
|||||||
MACS(MAC_TESTLIST, X) \
|
MACS(MAC_TESTLIST, X) \
|
||||||
HASHES(HASH_TESTLIST, X) \
|
HASHES(HASH_TESTLIST, X) \
|
||||||
X(argon2) \
|
X(argon2) \
|
||||||
|
X(primegen_probabilistic) \
|
||||||
/* end of list */
|
/* end of list */
|
||||||
|
|
||||||
static void test_mp_get_nbits(void)
|
static void test_mp_get_nbits(void)
|
||||||
@ -1476,6 +1519,54 @@ static void test_argon2(void)
|
|||||||
strbuf_free(outdata);
|
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[] = {
|
static const struct test tests[] = {
|
||||||
#define STRUCT_TEST(X) { #X, test_##X },
|
#define STRUCT_TEST(X) { #X, test_##X },
|
||||||
TESTLIST(STRUCT_TEST)
|
TESTLIST(STRUCT_TEST)
|
||||||
|
@ -99,7 +99,7 @@ target_link_libraries(cgtest
|
|||||||
|
|
||||||
add_executable(testsc
|
add_executable(testsc
|
||||||
${CMAKE_SOURCE_DIR}/testsc.c)
|
${CMAKE_SOURCE_DIR}/testsc.c)
|
||||||
target_link_libraries(testsc crypto utils)
|
target_link_libraries(testsc keygen crypto utils)
|
||||||
|
|
||||||
add_executable(testzlib
|
add_executable(testzlib
|
||||||
${CMAKE_SOURCE_DIR}/testzlib.c
|
${CMAKE_SOURCE_DIR}/testzlib.c
|
||||||
|
Loading…
Reference in New Issue
Block a user