diff --git a/wolfssl-gnutls-wrapper/Makefile b/wolfssl-gnutls-wrapper/Makefile index b034ca6..bf3b194 100644 --- a/wolfssl-gnutls-wrapper/Makefile +++ b/wolfssl-gnutls-wrapper/Makefile @@ -45,13 +45,13 @@ endif UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) - CFLAGS += -DENABLE_WOLFSSL -fPIC -Wall -Wextra + CFLAGS += -DENABLE_WOLFSSL -fPIC -Wall -Wextra -pthread CFLAGS_DEBUG += -g # Use rpath to wherever we actually link from LDFLAGS += -Wl,--no-as-needed -Wl,-z,now \ -Wl,-rpath,$(GNUTLS_LIBDIR) -Wl,-rpath,$(WOLFSSL_LIBDIR) SOFLAG := -shared - DL_LIBS := -ldl + DL_LIBS := -ldl -pthread endif ifeq ($(UNAME_S),Darwin) diff --git a/wolfssl-gnutls-wrapper/src/pk.c b/wolfssl-gnutls-wrapper/src/pk.c index bb0090b..e90ede5 100644 --- a/wolfssl-gnutls-wrapper/src/pk.c +++ b/wolfssl-gnutls-wrapper/src/pk.c @@ -736,8 +736,10 @@ static int wolfssl_pk_encrypt_rsa(gnutls_datum_t *ciphertext, } /* Encrypt using RSA PKCS#1 v1.5 padding */ + wolfssl_rng_lock(); ret = wc_RsaPublicEncrypt(plaintext->data, plaintext->size, ciphertext->data, ciphertext->size, &rsa, &priv_rng); + wolfssl_rng_unlock(); /* No longer need RSA key. */ wc_FreeRsaKey(&rsa); if (ret < 0) { @@ -808,10 +810,12 @@ static int wolfssl_pk_encrypt_rsa_oaep(gnutls_datum_t *ciphertext, } /* Encrypt using RSA PKCS#1 OAEP. */ + wolfssl_rng_lock(); ret = wc_RsaPublicEncrypt_ex(plaintext->data, plaintext->size, ciphertext->data, ciphertext->size, &rsa, &priv_rng, WC_RSA_OAEP_PAD, hash_type, mgf, params->spki.rsa_oaep_label.data, params->spki.rsa_oaep_label.size); + wolfssl_rng_unlock(); /* No longer need RSA key. */ wc_FreeRsaKey(&rsa); if (ret < 0) { @@ -962,8 +966,10 @@ static int wolfssl_pk_decrypt_rsa(gnutls_datum_t *plaintext, PRIVATE_KEY_UNLOCK(); /* Decrypt using RSA PKCS#1 v1.5 padding */ + wolfssl_rng_lock(); ret = wc_RsaPrivateDecrypt(ciphertext->data, ciphertext->size, plain, plain_size, &rsa); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -1076,9 +1082,11 @@ static int wolfssl_pk_decrypt_rsa_oaep(gnutls_datum_t *plaintext, PRIVATE_KEY_UNLOCK(); /* Decrypt using RSA PKCS#1 OAEP. */ + wolfssl_rng_lock(); ret = wc_RsaPrivateDecrypt_ex(ciphertext->data, ciphertext->size, plain, plain_size, &rsa, WC_RSA_OAEP_PAD, hash_type, mgf, params->spki.rsa_oaep_label.data, params->spki.rsa_oaep_label.size); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -1275,8 +1283,10 @@ static int wolfssl_pk_sign_rsa(gnutls_datum_t *signature, PRIVATE_KEY_UNLOCK(); /* Decrypt using RSA PKCS#1 v1.5. */ + wolfssl_rng_lock(); ret = wc_RsaSSL_Sign(vdata->data, vdata->size, signature->data, signature->size, &rsa, &priv_rng); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -1352,9 +1362,11 @@ static int wolfssl_pk_sign_rsa_pss(gnutls_datum_t *signature, PRIVATE_KEY_UNLOCK(); /* Decrypt using RSA PKCS#1 PSS. */ + wolfssl_rng_lock(); ret = wc_RsaPSS_Sign_ex(vdata->data, vdata->size, signature->data, signature->size, hash_type, mgf, sign_params->salt_size, &rsa, &priv_rng); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -1436,8 +1448,10 @@ static int wolfssl_pk_sign_ecc(gnutls_datum_t *signature, PRIVATE_KEY_UNLOCK(); /* Sign hash using ECDSA. */ + wolfssl_rng_lock(); ret = wc_ecc_sign_hash(vdata->data, vdata->size, signature->data, &len, &priv_rng, &ecc); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2487,11 +2501,6 @@ static int wolfssl_pk_generate_keys_rsa(unsigned int bits, } #endif -#ifdef WC_RNG_SEED_CB - /* Set the seed callback to get entropy. */ - wc_SetSeed_Cb(wc_GenerateSeed); -#endif - /* Initialize RSA key */ ret = wc_InitRsaKey(&rsa, NULL); if (ret != 0) { @@ -2502,7 +2511,9 @@ static int wolfssl_pk_generate_keys_rsa(unsigned int bits, PRIVATE_KEY_UNLOCK(); /* Generate RSA key */ + wolfssl_rng_lock(); ret = wc_MakeRsaKey(&rsa, bits, WC_RSA_EXPONENT, &priv_rng); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2549,11 +2560,6 @@ static int wolfssl_pk_generate_keys_dh(unsigned int bits, if (wolfssl_ensure_rng() != 0) return GNUTLS_E_RANDOM_FAILED; -#ifdef WC_RNG_SEED_CB - /* Set the seed callback to get entropy. */ - wc_SetSeed_Cb(wc_GenerateSeed); -#endif - /* Convert private key size to public key size. */ if (bits == 256) { bits = 2048; @@ -2610,7 +2616,9 @@ static int wolfssl_pk_generate_keys_dh(unsigned int bits, PRIVATE_KEY_UNLOCK(); /* Generate public/private key pair for DH. */ + wolfssl_rng_lock(); ret = wc_DhGenerateKeyPair(&dh, &priv_rng, priv, &privSz, pub, &pubSz); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2673,11 +2681,6 @@ static int wolfssl_pk_generate_keys_ecc(unsigned int curve, return ret; } -#ifdef WC_RNG_SEED_CB - /* Set the seed callback to get entropy. */ - wc_SetSeed_Cb(wc_GenerateSeed); -#endif - /* Initialize ECC key */ ret = wc_ecc_init(&ecc); if (ret != 0) { @@ -2688,7 +2691,9 @@ static int wolfssl_pk_generate_keys_ecc(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate ECC key */ + wolfssl_rng_lock(); ret = wc_ecc_make_key_ex(&priv_rng, curve_size, &ecc, curve_id); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2749,7 +2754,9 @@ static int wolfssl_pk_generate_keys_ed25519(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate Ed25519 key */ + wolfssl_rng_lock(); ret = wc_ed25519_make_key(&priv_rng, ED25519_KEY_SIZE, &ed25519); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2851,7 +2858,9 @@ static int wolfssl_pk_generate_keys_ed448(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate Ed448 key */ + wolfssl_rng_lock(); ret = wc_ed448_make_key(&priv_rng, ED448_KEY_SIZE, &ed448); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2953,7 +2962,9 @@ static int wolfssl_pk_generate_keys_x25519(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate X25519 key */ + wolfssl_rng_lock(); ret = wc_curve25519_make_key(&priv_rng, CURVE25519_KEYSIZE, &x25519); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -3055,7 +3066,9 @@ static int wolfssl_pk_generate_keys_x448(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate X448 key */ + wolfssl_rng_lock(); ret = wc_curve448_make_key(&priv_rng, CURVE448_KEY_SIZE, &x448); + wolfssl_rng_unlock(); /* Random number generator no longer needed. */ PRIVATE_KEY_LOCK(); @@ -4117,7 +4130,9 @@ static int wolfssl_pk_derive_ecc(gnutls_datum_t *out, PRIVATE_KEY_UNLOCK(); /* Calculate the shared secret. */ + wolfssl_rng_lock(); ret = wc_ecc_shared_secret(&private, &public, out->data, &len); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -4246,8 +4261,10 @@ static int wolfssl_pk_derive_x25519(gnutls_datum_t *out, PRIVATE_KEY_UNLOCK(); /* Calculate the shared secret. */ + wolfssl_rng_lock(); ret = wc_curve25519_shared_secret_ex(&private, &public, out->data, &len, EC25519_LITTLE_ENDIAN); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); diff --git a/wolfssl-gnutls-wrapper/src/random.c b/wolfssl-gnutls-wrapper/src/random.c index c52c4bb..4b0e01a 100644 --- a/wolfssl-gnutls-wrapper/src/random.c +++ b/wolfssl-gnutls-wrapper/src/random.c @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef ENABLE_WOLFSSL /** Context structure for wolfSSL RNG. */ @@ -24,19 +25,36 @@ pid_t pid = -1; /** Indicates if the DBRG needs to be reseeded.*/ int rng_ready = 0; -/** Ensures that only one process at the time is accessing the RNGs objects */ +/** Serializes all use of the RNG objects above. A WC_RNG is not thread safe + * unless wolfSSL locks it itself (5.9.4 and later, unless built with + * --disable-rng-autolock). */ static wolfSSL_Mutex wc_rng_lock; -/** indicates that the rng lock object was actually initialized */ -static int wc_rng_lock_init = 0; +/** Initializes wc_rng_lock exactly once, whichever thread gets there first. */ +static pthread_once_t wc_rng_lock_once = PTHREAD_ONCE_INIT; -/** Gets called every time we need to do a lock operation, - * initializes the lock object if it wasn't already. */ -static inline void wc_rng_lock_once(void) +static void wc_rng_lock_init(void) { - if (!wc_rng_lock_init) { - wc_InitMutex(&wc_rng_lock); - wc_rng_lock_init = 1; - } + wc_InitMutex(&wc_rng_lock); +} + +/** + * Take the lock that guards priv_rng and pub_rng. + * + * Hold it across every wolfCrypt call that uses one of them, including calls + * on a key that keeps a pointer to one (wc_RsaSetRNG(), ecc_key.rng, + * wc_curve25519_set_rng()). Not recursive: do not call wolfssl_ensure_rng() + * while holding it. + */ +void wolfssl_rng_lock(void) +{ + pthread_once(&wc_rng_lock_once, wc_rng_lock_init); + wc_LockMutex(&wc_rng_lock); +} + +/** Release the lock taken by wolfssl_rng_lock(). */ +void wolfssl_rng_unlock(void) +{ + wc_UnLockMutex(&wc_rng_lock); } /* Seed DRBGs on first use or after a fork */ @@ -46,8 +64,7 @@ int wolfssl_ensure_rng(void) pid_t p = getpid(); - wc_rng_lock_once(); - wc_LockMutex(&wc_rng_lock); + wolfssl_rng_lock(); /* We check if the pid is different (a fork happened) * or if the the first time creating a context (a first seed is needed). */ @@ -58,6 +75,9 @@ int wolfssl_ensure_rng(void) * do a reseed. */ wc_FreeRng(&priv_rng); wc_FreeRng(&pub_rng); + /* not ready until both are re-initialized: a failed reseed + * must not leave freed DRBGs marked usable */ + rng_ready = 0; } #ifdef WC_RNG_SEED_CB @@ -65,11 +85,13 @@ int wolfssl_ensure_rng(void) #endif if (wc_InitRng(&priv_rng) != 0) { + wolfssl_rng_unlock(); return GNUTLS_E_RANDOM_FAILED; } if (wc_InitRng(&pub_rng) != 0) { wc_FreeRng(&priv_rng); + wolfssl_rng_unlock(); return GNUTLS_E_RANDOM_FAILED; } @@ -77,7 +99,7 @@ int wolfssl_ensure_rng(void) rng_ready = 1; } - wc_UnLockMutex(&wc_rng_lock); + wolfssl_rng_unlock(); return 0; } @@ -142,8 +164,7 @@ static int wolfssl_rnd(void *_ctx, int level, void *data, size_t datasize) return GNUTLS_E_RANDOM_FAILED; } - wc_rng_lock_once(); - wc_LockMutex(&wc_rng_lock); + wolfssl_rng_lock(); /* clear output buffer before filling */ XMEMSET(data, 0, datasize); @@ -154,7 +175,7 @@ static int wolfssl_rnd(void *_ctx, int level, void *data, size_t datasize) ret = wc_RNG_GenerateBlock(rng, data, size); if (ret != 0) { - wc_UnLockMutex(&wc_rng_lock); + wolfssl_rng_unlock(); WGW_ERROR("Requested %d bytes", size); WGW_WOLFSSL_ERROR("wc_RNG_GenerateBlock", ret); return GNUTLS_E_RANDOM_FAILED; @@ -165,7 +186,7 @@ static int wolfssl_rnd(void *_ctx, int level, void *data, size_t datasize) datasize -= size; } while (datasize > 0); - wc_UnLockMutex(&wc_rng_lock); + wolfssl_rng_unlock(); return 0; } @@ -183,7 +204,9 @@ static void wolfssl_rnd_refresh(void *_ctx) /* this forces a reseed of the random number generators * on the next call (wolfssl_ensure_rng). */ + wolfssl_rng_lock(); rng_ready = 0; + wolfssl_rng_unlock(); } /** diff --git a/wolfssl-gnutls-wrapper/src/random.h b/wolfssl-gnutls-wrapper/src/random.h index 18b56a8..3d47650 100644 --- a/wolfssl-gnutls-wrapper/src/random.h +++ b/wolfssl-gnutls-wrapper/src/random.h @@ -5,4 +5,6 @@ extern WC_RNG pub_rng; extern pid_t pid; extern int rng_ready; int wolfssl_ensure_rng(void); +void wolfssl_rng_lock(void); +void wolfssl_rng_unlock(void); #endif diff --git a/wolfssl-gnutls-wrapper/tests/Makefile b/wolfssl-gnutls-wrapper/tests/Makefile index f7fe39d..1d86dc9 100644 --- a/wolfssl-gnutls-wrapper/tests/Makefile +++ b/wolfssl-gnutls-wrapper/tests/Makefile @@ -1,4 +1,4 @@ -TESTS = test_hash test_shake test_aescbc test_aescfb8 test_aesgcm test_aesccm test_aesxts test_hmac test_cmac test_gmac test_rnd test_rnd_fork test_tls_prf test_hkdf test_pbkdf2 test_ecdsa_sign_and_verify test_ecdh_encrypt_and_decrypt test_eddsa_sign_and_verify test_rsa_sign_and_verify test_rsa_encrypt_and_decrypt test_dh_encrypt_and_decrypt test_pk_import_export test_long_hash test_fips test_aessiv +TESTS = test_hash test_shake test_aescbc test_aescfb8 test_aesgcm test_aesccm test_aesxts test_hmac test_cmac test_gmac test_rnd test_rnd_fork test_tls_prf test_hkdf test_pbkdf2 test_ecdsa_sign_and_verify test_ecdh_encrypt_and_decrypt test_eddsa_sign_and_verify test_rsa_sign_and_verify test_rsa_encrypt_and_decrypt test_dh_encrypt_and_decrypt test_pk_import_export test_long_hash test_fips test_aessiv test_pk_threads PKGCONF ?= pkg-config UNAME_S := $(shell uname -s) @@ -62,9 +62,9 @@ ifeq ($(UNAME_S),Darwin) -L$(GNUTLS_LIBDIR) endif -# Libraries (dl only on Linux) +# Libraries (dl and pthread only on Linux) ifeq ($(UNAME_S),Linux) - LIBS = $(GNUTLS_LIBS) -ldl + LIBS = $(GNUTLS_LIBS) -ldl -pthread else LIBS = $(GNUTLS_LIBS) endif diff --git a/wolfssl-gnutls-wrapper/tests/test_pk_threads.c b/wolfssl-gnutls-wrapper/tests/test_pk_threads.c new file mode 100644 index 0000000..d2faf34 --- /dev/null +++ b/wolfssl-gnutls-wrapper/tests/test_pk_threads.c @@ -0,0 +1,187 @@ +/* Concurrent EC key generation and ECDSA signing from several threads. + * + * The provider shares one DRBG across the process. If two threads use it at + * once its state is corrupted and they can draw the same output: the same + * private key, or the same ECDSA nonce (which reveals the key). Nothing fails + * visibly, so this test records every private scalar and every r value and + * checks that none repeats; with a working DRBG a repeat has negligible + * probability. + */ +#include +#include +#include + +#include +#include +#include + +#include "test_util.h" + +#define NUM_THREADS 8 +#define VALUE_MAX 72 + +struct value { + unsigned char data[VALUE_MAX]; + unsigned int len; +}; + +static int iterations; +/* One slot per thread and iteration, compared only after all threads joined + * so that the test adds no synchronization of its own. */ +static struct value *priv_values; +static struct value *r_values; + +static int record(struct value *v, const gnutls_datum_t *d) +{ + if (d->size == 0 || d->size > VALUE_MAX) { + return 1; + } + memcpy(v->data, d->data, d->size); + v->len = d->size; + return 0; +} + +static int keygen_and_sign(int slot) +{ + gnutls_privkey_t privkey = NULL; + gnutls_pubkey_t pubkey = NULL; + gnutls_datum_t msg = { (unsigned char *)"test message", 12 }; + gnutls_datum_t sig = { NULL, 0 }; + gnutls_datum_t k = { NULL, 0 }; + gnutls_datum_t r = { NULL, 0 }; + gnutls_datum_t s = { NULL, 0 }; + int ret; + + if ((ret = gnutls_privkey_init(&privkey)) < 0 || + (ret = gnutls_privkey_generate(privkey, GNUTLS_PK_ECDSA, + GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0)) < 0 || + (ret = gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &msg, + &sig)) < 0 || + (ret = gnutls_pubkey_init(&pubkey)) < 0 || + (ret = gnutls_pubkey_import_privkey(pubkey, privkey, 0, 0)) < 0 || + (ret = gnutls_pubkey_verify_data2(pubkey, GNUTLS_SIGN_ECDSA_SHA256, 0, + &msg, &sig)) < 0 || + (ret = gnutls_privkey_export_ecc_raw2(privkey, NULL, NULL, NULL, &k, + 0)) < 0 || + (ret = gnutls_decode_rs_value(&sig, &r, &s)) < 0) { + print_gnutls_error("generating, signing or verifying", ret); + ret = 1; + } + else if (record(&priv_values[slot], &k) != 0 || + record(&r_values[slot], &r) != 0) { + fprintf(stderr, "Unexpected private key or r size\n"); + ret = 1; + } + else { + ret = 0; + } + + gnutls_free(k.data); + gnutls_free(r.data); + gnutls_free(s.data); + gnutls_free(sig.data); + gnutls_pubkey_deinit(pubkey); + gnutls_privkey_deinit(privkey); + return ret; +} + +static void *thread_main(void *arg) +{ + int id = *(int *)arg; + long failed = 0; + int i; + + for (i = 0; i < iterations; i++) { + failed += keygen_and_sign(id * iterations + i); + } + + return (void *)failed; +} + +static int value_cmp(const void *a, const void *b) +{ + const struct value *x = a; + const struct value *y = b; + + if (x->len != y->len) { + return x->len < y->len ? -1 : 1; + } + return memcmp(x->data, y->data, x->len); +} + +static int count_repeats(struct value *values, int n) +{ + int repeats = 0; + int i; + + qsort(values, n, sizeof(*values), value_cmp); + for (i = 1; i < n; i++) { + /* Slots of failed operations are empty and counted as failures. */ + if (values[i].len != 0 && + value_cmp(&values[i], &values[i - 1]) == 0) { + repeats++; + } + } + + return repeats; +} + +int main(int argc, char *argv[]) +{ + pthread_t threads[NUM_THREADS]; + int ids[NUM_THREADS]; + int failed = 0; + int priv_repeats; + int r_repeats; + int ret; + int i; + + iterations = (argc > 1 && strcmp(argv[1], "-fast") == 0) ? 250 : 1000; + + printf("Testing concurrent EC key generation and ECDSA signing " + "(%d threads x %d)...\n", NUM_THREADS, iterations); + + ret = gnutls_global_init(); + if (ret != 0) { + print_gnutls_error("initializing GnuTLS", ret); + return 1; + } + + priv_values = calloc(NUM_THREADS * iterations, sizeof(*priv_values)); + r_values = calloc(NUM_THREADS * iterations, sizeof(*r_values)); + if (priv_values == NULL || r_values == NULL) { + fprintf(stderr, "Allocating memory failed\n"); + return 1; + } + + for (i = 0; i < NUM_THREADS; i++) { + ids[i] = i; + if (pthread_create(&threads[i], NULL, thread_main, &ids[i]) != 0) { + fprintf(stderr, "Creating thread %d failed\n", i); + return 1; + } + } + for (i = 0; i < NUM_THREADS; i++) { + void *res; + + pthread_join(threads[i], &res); + failed += (int)(long)res; + } + + priv_repeats = count_repeats(priv_values, NUM_THREADS * iterations); + r_repeats = count_repeats(r_values, NUM_THREADS * iterations); + printf("operations failed: %d, repeated private keys: %d, " + "repeated ECDSA r values: %d\n", failed, priv_repeats, r_repeats); + + free(priv_values); + free(r_values); + gnutls_global_deinit(); + + if (failed != 0 || priv_repeats != 0 || r_repeats != 0) { + printf("FAILURE - concurrent key generation and signing\n"); + return 1; + } + + printf("SUCCESS - concurrent key generation and signing\n"); + return 0; +}