From b37e882bc6d2edc583b7f2f3756fdea8cd788dde Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 13:10:47 +0200 Subject: [PATCH 01/23] x509storeissuer: factor out usage printing Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 941d432..66af06b 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -68,6 +68,15 @@ static void do_x509storeissuer(size_t num) X509_STORE_CTX_free(ctx); } +static void +usage(char * const argv[]) +{ + fprintf(stderr, + "Usage: %s [-t] certsdir threadcount\n" + "-t - terse output\n", + basename(argv[0])); +} + int main(int argc, char *argv[]) { int i; @@ -86,8 +95,7 @@ int main(int argc, char *argv[]) terse = 1; break; default: - printf("Usage: %s [-t] certsdir threadcount\n", basename(argv[0])); - printf("-t - terse output\n"); + usage(argv); return EXIT_FAILURE; } } From c2010511e27ab01dc6014153176c90f3a3e746ec Mon Sep 17 00:00:00 2001 From: sashan Date: Mon, 29 Sep 2025 14:33:19 +0200 Subject: [PATCH 02/23] s/err/error where apropriate easiest way to fix liner issues on windows --- source/evp_fetch.c | 22 +++++++++++----------- source/evp_setpeer.c | 12 ++++++------ source/handshake.c | 18 +++++++++--------- source/newrawkey.c | 6 +++--- source/pkeyread.c | 16 ++++++++-------- source/randbytes.c | 6 +++--- source/rsasign.c | 6 +++--- source/rwlocks.c | 4 ++-- source/sslnew.c | 6 +++--- source/writeread.c | 14 +++++++------- source/x509storeissuer.c | 9 +++++---- 11 files changed, 60 insertions(+), 59 deletions(-) diff --git a/source/evp_fetch.c b/source/evp_fetch.c index 70b35b7..578c0e6 100644 --- a/source/evp_fetch.c +++ b/source/evp_fetch.c @@ -61,7 +61,7 @@ size_t *counts; OSSL_TIME max_time; -int err = 0; +int error = 0; int pq = 0; static int threadcount; @@ -194,7 +194,7 @@ void do_fetch(size_t num) fetch_alg = exclusive_fetch_alg; } - if (err == 1) + if (error == 1) return; switch (j) { @@ -203,7 +203,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (md == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_MD_free(md); @@ -214,7 +214,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (cph == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_CIPHER_free(cph); @@ -225,7 +225,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (kdf == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_KDF_free(kdf); @@ -236,7 +236,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (mac == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_MAC_free(mac); @@ -247,7 +247,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (rnd == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_RAND_free(rnd); @@ -258,7 +258,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (kem == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_KEM_free(kem); @@ -269,14 +269,14 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (sig == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_SIGNATURE_free(sig); break; } default: - err = 1; + error = 1; return; } counts[num]++; @@ -372,7 +372,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/evp_setpeer.c b/source/evp_setpeer.c index 302db5d..5bb0c78 100644 --- a/source/evp_setpeer.c +++ b/source/evp_setpeer.c @@ -24,7 +24,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; size_t num_calls; static int threadcount; @@ -42,13 +42,13 @@ void do_setpeer(size_t num) pkey_ctx = EVP_PKEY_CTX_new(pkey, NULL); if (pkey_ctx == NULL) { - err = 1; + error = 1; printf("Failed to create pkey_ctx\n"); return; } if (EVP_PKEY_derive_init(pkey_ctx) <= 0) { - err = 1; + error = 1; printf("Failed to init pkey_ctx\n"); EVP_PKEY_CTX_free(pkey_ctx); return; @@ -58,7 +58,7 @@ void do_setpeer(size_t num) do { if (EVP_PKEY_derive_set_peer(pkey_ctx, pkey) <= 0) { - err = 1; + error = 1; break; } counts[num]++; @@ -97,7 +97,7 @@ static double get_avcalltime(void) static void report_result(int key_id, int terse) { - if (err) { + if (error) { fprintf(stderr, "Error during test of %s\n", sample_names[key_id]); exit(EXIT_FAILURE); @@ -228,7 +228,7 @@ int main(int argc, char *argv[]) EVP_PKEY_free(pkey); } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/handshake.c b/source/handshake.c index d64b034..a33d6f9 100644 --- a/source/handshake.c +++ b/source/handshake.c @@ -25,7 +25,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; typedef enum { INIT_LIB_CTX, @@ -102,7 +102,7 @@ static void do_handshake(size_t num) } while (time.t < max_time.t); if (!ret) - err = 1; + error = 1; } static void do_handshake_ossl_lib_ctx_per_thread(size_t num) @@ -117,7 +117,7 @@ static void do_handshake_ossl_lib_ctx_per_thread(size_t num) libctx = OSSL_LIB_CTX_new(); if (libctx == NULL) { fprintf(stderr, "%s:%d: Failed to create ossl lib context\n", __FILE__, __LINE__); - err = 1; + error = 1; return; } @@ -132,7 +132,7 @@ static void do_handshake_ossl_lib_ctx_per_thread(size_t num) privkey)) { ERR_print_errors_fp(stderr); fprintf(stderr, "%s:%d: Failed to create SSL_CTX pair\n", __FILE__, __LINE__); - err = 1; + error = 1; return; } } @@ -157,7 +157,7 @@ static void do_handshake_ossl_lib_ctx_per_thread(size_t num) SSL_CTX_free(lcctx); if (!ret) - err = 1; + error = 1; OSSL_LIB_CTX_free(libctx); } @@ -187,7 +187,7 @@ static void do_handshake_ctx_pool(size_t num) privkey)) { ERR_print_errors_fp(stderr); fprintf(stderr, "%s:%d: Failed to create SSL_CTX pair\n", __FILE__, __LINE__); - err = 1; + error = 1; return; } } @@ -203,7 +203,7 @@ static void do_handshake_ctx_pool(size_t num) privkey)) { ERR_print_errors_fp(stderr); fprintf(stderr, "%s:%d: Failed to create SSL_CTX pair\n", __FILE__, __LINE__); - err = 1; + error = 1; return; } } @@ -230,7 +230,7 @@ static void do_handshake_ctx_pool(size_t num) } if (!ret) - err = 1; + error = 1; } static void free_ctx_pool() @@ -482,7 +482,7 @@ int main(int argc, char * const argv[]) goto err; }; - if (err) { + if (error) { printf("Error during test\n"); goto err; } diff --git a/source/newrawkey.c b/source/newrawkey.c index 3b380a9..ed9e112 100644 --- a/source/newrawkey.c +++ b/source/newrawkey.c @@ -37,7 +37,7 @@ enum { } algorithm = ALGO_X25519; const char *alg_name = "X25519"; -int err = 0; +int error = 0; static unsigned char key_x25519[32] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, @@ -377,7 +377,7 @@ void do_newrawkey(size_t num) sizeof(key_data)); #endif if (pkey == NULL) - err = 1; + error = 1; else EVP_PKEY_free(pkey); counts[num]++; @@ -451,7 +451,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/pkeyread.c b/source/pkeyread.c index e04c131..c671a61 100644 --- a/source/pkeyread.c +++ b/source/pkeyread.c @@ -50,7 +50,7 @@ size_t num_calls; size_t *counts; OSSL_TIME max_time; -int err = 0; +int error = 0; static int threadcount; @@ -69,7 +69,7 @@ static void do_pemread(size_t num) if (sample_id >= SAMPLE_ALL) { fprintf(stderr, "%s no sample key set for test\n", __func__); - err = 1; + error = 1; return; } @@ -80,7 +80,7 @@ static void do_pemread(size_t num) if (pem == NULL) { fprintf(stderr, "%s Cannot create mem BIO [%s PEM]\n", __func__, sample_names[sample_id]); - err = 1; + error = 1; return; } @@ -93,14 +93,14 @@ static void do_pemread(size_t num) if (key == NULL) { fprintf(stderr, "Failed to create key [%s PEM]\n", sample_names[sample_id]); - err = 1; + error = 1; goto end; } EVP_PKEY_free(key); if (BIO_reset(pem) == 0) { fprintf(stderr, "Failed to reset BIO [%s PEM]\n", sample_names[sample_id]); - err = 1; + error = 1; goto end; } @@ -130,7 +130,7 @@ static void do_derread(size_t num) if (sample_id >= SAMPLE_ALL) { fprintf(stderr, "%s no sample key set for test\n", __func__); - err = 1; + error = 1; return; } @@ -144,7 +144,7 @@ static void do_derread(size_t num) if (pkey == NULL) { fprintf(stderr, "%s pkey is NULL [%s DER]\n", __func__, sample_names[sample_id]); - err = 1; + error = 1; goto error; } error: @@ -238,7 +238,7 @@ static void report_result(int key_id, int format_id, int verbosity) { struct call_times times = { 0 }; - if (err) { + if (error) { fprintf(stderr, "Error during test of %s in %s format\n", sample_names[key_id], format_names[format_id]); exit(EXIT_FAILURE); diff --git a/source/randbytes.c b/source/randbytes.c index a72881a..85761f3 100644 --- a/source/randbytes.c +++ b/source/randbytes.c @@ -28,7 +28,7 @@ size_t num_calls; size_t *counts; OSSL_TIME max_time; -int err = 0; +int error = 0; static int threadcount; @@ -42,7 +42,7 @@ void do_randbytes(size_t num) do { if (!RAND_bytes(buf, sizeof(buf))) - err = 1; + error = 1; counts[num]++; time = ossl_time_now(); } while (time.t < max_time.t); @@ -92,7 +92,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/rsasign.c b/source/rsasign.c index b683c54..0097eb7 100644 --- a/source/rsasign.c +++ b/source/rsasign.c @@ -26,7 +26,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; EVP_PKEY *rsakey = NULL; size_t *counts; @@ -61,7 +61,7 @@ void do_rsasign(size_t num) if (EVP_PKEY_sign_init(ctx) <= 0 || EVP_PKEY_sign(ctx, sig, &siglen, (const unsigned char*)tbs, SHA_DIGEST_LENGTH) <= 0) { - err = 1; + error = 1; break; } counts[num]++; @@ -130,7 +130,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/rwlocks.c b/source/rwlocks.c index 176c4d9..27747d2 100644 --- a/source/rwlocks.c +++ b/source/rwlocks.c @@ -26,7 +26,7 @@ #define RUN_TIME 5 size_t threadcount = 0; -int err = 0; +int error = 0; unsigned long *dataval = NULL; int writers = 0; int readers = 0; @@ -161,7 +161,7 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - if (err) { + if (error) { printf("Error during test\n"); return EXIT_FAILURE; } diff --git a/source/sslnew.c b/source/sslnew.c index 4dd815c..598cfaf 100644 --- a/source/sslnew.c +++ b/source/sslnew.c @@ -24,7 +24,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; static SSL_CTX *ctx; static int threadcount; @@ -46,7 +46,7 @@ void do_sslnew(size_t num) wbio = BIO_new(BIO_s_mem()); if (s == NULL || rbio == NULL || wbio == NULL) { - err = 1; + error = 1; BIO_free(rbio); BIO_free(wbio); } else { @@ -111,7 +111,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/writeread.c b/source/writeread.c index de324f4..d276ad2 100644 --- a/source/writeread.c +++ b/source/writeread.c @@ -23,7 +23,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; static SSL_CTX *sctx = NULL, *cctx = NULL; static int share_ctx = 1; @@ -57,7 +57,7 @@ static void do_writeread(size_t num) if (!perflib_create_ssl_ctx_pair(smethod, cmethod, 0, 0, &lsctx, &lcctx, cert, privkey)) { fprintf(stderr, "Failed to create SSL_CTX pair\n"); - err = 1; + error = 1; return; } } @@ -68,7 +68,7 @@ static void do_writeread(size_t num) ret &= perflib_create_bare_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE); if (!ret) { - err = 1; + error = 1; return; } @@ -76,18 +76,18 @@ static void do_writeread(size_t num) size_t written = 0; if (SSL_write_ex(clientssl, cbuf, buf_size, &written) <= 0) { fprintf(stderr, "Failed to write data\n"); - err = 1; + error = 1; return; } size_t readbytes; if (SSL_read_ex(serverssl, sbuf, buf_size, &readbytes) <= 0) { fprintf(stderr, "Failed to read data\n"); - err = 1; + error = 1; return; } if (readbytes != written) { fprintf(stderr, "Failed to read %ld bytes, got %ld\n", written, readbytes); - err = 1; + error = 1; return; } counts[num]++; @@ -190,7 +190,7 @@ int main(int argc, char * const argv[]) goto err; } - if (err) { + if (error) { fprintf(stderr, "Error during test\n"); goto err; } diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 66af06b..4f2b49c 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -20,11 +20,12 @@ #endif /* _WIN32 */ #include #include +#include "perflib/err.h" #include "perflib/perflib.h" #define RUN_TIME 5 -static int err = 0; +static int error = 0; static X509_STORE *store = NULL; static X509 *x509 = NULL; @@ -41,7 +42,7 @@ static void do_x509storeissuer(size_t num) if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509, NULL)) { printf("Failed to initialise X509_STORE_CTX\n"); - err = 1; + error = 1; goto err; } @@ -55,7 +56,7 @@ static void do_x509storeissuer(size_t num) */ if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509) != 0) { printf("Unexpected result from X509_STORE_CTX_get1_issuer\n"); - err = 1; + error = 1; X509_free(issuer); goto err; } @@ -153,7 +154,7 @@ int main(int argc, char *argv[]) goto err; } - if (err) { + if (error) { printf("Error during test\n"); goto err; } From 948ffbd5d81eb1c68ae6b95f3fa5d2338135e0d4 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Fri, 26 Sep 2025 01:26:11 +0200 Subject: [PATCH 03/23] Use perflib/err.h unconditionally Signed-off-by: Eugene Syromiatnikov --- source/perflib/err.h | 10 +++++++++- source/ssl_poll_perf.c | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/source/perflib/err.h b/source/perflib/err.h index 0bb7a53..eb47e32 100644 --- a/source/perflib/err.h +++ b/source/perflib/err.h @@ -11,7 +11,13 @@ # define OSSL_PERFLIB_ERR_H # pragma once -#include +# if !defined(_WIN32) + +# include + +# else /* _WIN32 */ + +# include extern const char *progname; @@ -19,5 +25,7 @@ extern void vwarnx(const char *, va_list); extern void errx(int, const char *, ...); extern void warnx(const char *, ...); +# endif /* !_WIN32 */ + #endif diff --git a/source/ssl_poll_perf.c b/source/ssl_poll_perf.c index 378058d..dbcca81 100644 --- a/source/ssl_poll_perf.c +++ b/source/ssl_poll_perf.c @@ -33,7 +33,6 @@ #ifdef _WIN32 /* Windows */ # include #else /* Linux/Unix */ -# include # include # include # include @@ -54,10 +53,11 @@ #else # include # include "perflib/basename.h" -# include "perflib/err.h" # include "perflib/getopt.h" #endif /* _WIN32 */ +#include "perflib/err.h" + /* * The code here is based on QUIC poll server found in demos/quic/poll-server * in OpenSSL source code repository. Here we take the demo one step further From 2b3b804e92d411922728b1bd517dbe1cac60816e Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Fri, 26 Sep 2025 01:28:17 +0200 Subject: [PATCH 04/23] perflib: add vwarn/err/warn Signed-off-by: Eugene Syromiatnikov --- source/perflib/err.c | 36 ++++++++++++++++++++++++++++++++++++ source/perflib/err.h | 5 +++++ 2 files changed, 41 insertions(+) diff --git a/source/perflib/err.c b/source/perflib/err.c index 3a3164d..59764ff 100644 --- a/source/perflib/err.c +++ b/source/perflib/err.c @@ -7,6 +7,7 @@ * https://www.openssl.org/source/license.html */ +#include #include #include #include @@ -22,6 +23,20 @@ vwarnx(const char *fmt, va_list ap) putc('\n', stderr); } +void +vwarn(const char *fmt, va_list ap) +{ + int saved_errno = errno; + + if (progname != NULL) + fprintf(stderr, "%s: ", progname); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": "); + + errno = saved_errno; + perror(NULL); +} + void errx(int status, const char *fmt, ...) { @@ -33,6 +48,17 @@ errx(int status, const char *fmt, ...) exit(status); } +void +err(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarn(fmt, ap); + va_end(ap); + exit(status); +} + void warnx(const char *fmt, ...) { @@ -42,3 +68,13 @@ warnx(const char *fmt, ...) vwarnx(fmt, ap); va_end(ap); } + +void +warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarn(fmt, ap); + va_end(ap); +} diff --git a/source/perflib/err.h b/source/perflib/err.h index eb47e32..5e848ee 100644 --- a/source/perflib/err.h +++ b/source/perflib/err.h @@ -22,8 +22,13 @@ extern const char *progname; extern void vwarnx(const char *, va_list); +extern void vwarn(const char *, va_list); + extern void errx(int, const char *, ...); +extern void err(int, const char *, ...); + extern void warnx(const char *, ...); +extern void warn(const char *, ...); # endif /* !_WIN32 */ From ec81797d71626cba28de587d940d30134432b773 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 13:33:02 +0200 Subject: [PATCH 05/23] x509storeissuer: use errx/warnx for error message output Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 69 ++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 4f2b49c..67b605c 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -41,7 +41,7 @@ static void do_x509storeissuer(size_t num) OSSL_TIME time; if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509, NULL)) { - printf("Failed to initialise X509_STORE_CTX\n"); + warnx("Failed to initialise X509_STORE_CTX"); error = 1; goto err; } @@ -55,7 +55,7 @@ static void do_x509storeissuer(size_t num) * against an empty store. */ if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509) != 0) { - printf("Unexpected result from X509_STORE_CTX_get1_issuer\n"); + warnx("Unexpected result from X509_STORE_CTX_get1_issuer"); error = 1; X509_free(issuer); goto err; @@ -101,63 +101,48 @@ int main(int argc, char *argv[]) } } - if (argv[optind] == NULL) { - printf("certsdir is missing\n"); - goto err; - } + if (argv[optind] == NULL) + errx(EXIT_FAILURE, "certsdir is missing"); + cert = perflib_mk_file_path(argv[optind], "servercert.pem"); - if (cert == NULL) { - printf("Failed to allocate cert\n"); - goto err; - } + if (cert == NULL) + errx(EXIT_FAILURE, "Failed to allocate cert path"); + optind++; - if (argv[optind] == NULL) { - printf("threadcount is missing\n"); - goto err; - } + if (argv[optind] == NULL) + errx(EXIT_FAILURE, "threadcount is missing"); + threadcount = atoi(argv[optind]); - if (threadcount < 1) { - printf("threadcount must be > 0\n"); - goto err; - } + if (threadcount < 1) + errx(EXIT_FAILURE, "threadcount must be > 0"); store = X509_STORE_new(); - if (store == NULL || !X509_STORE_set_default_paths(store)) { - printf("Failed to create X509_STORE\n"); - goto err; - } + if (store == NULL || !X509_STORE_set_default_paths(store)) + errx(EXIT_FAILURE, "Failed to create X509_STORE"); bio = BIO_new_file(cert, "rb"); - if (bio == NULL) { - printf("Unable to load certificate\n"); - goto err; - } + if (bio == NULL) + errx(EXIT_FAILURE, "Unable to load certificate\n"); + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); - if (x509 == NULL) { - printf("Failed to read certificate\n"); - goto err; - } + if (x509 == NULL) + errx(EXIT_FAILURE, "Failed to read certificate"); + BIO_free(bio); bio = NULL; counts = OPENSSL_malloc(sizeof(size_t) * threadcount); - if (counts == NULL) { - printf("Failed to create counts array\n"); - goto err; - } + if (counts == NULL) + errx(EXIT_FAILURE, "Failed to create counts array"); max_time = ossl_time_add(ossl_time_now(), ossl_seconds2time(RUN_TIME)); - if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) { - printf("Failed to run the test\n"); - goto err; - } + if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) + errx(EXIT_FAILURE, "Failed to run the test"); - if (error) { - printf("Error during test\n"); - goto err; - } + if (error) + errx(EXIT_FAILURE, "Error during test"); for (i = 0; i < threadcount; i++) total_count += counts[i]; From 7bbc21160de0164f25b1b6ac7e630289c935eccf Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 13:45:54 +0200 Subject: [PATCH 06/23] x509storeissuer: factor out integer parameter parsing into a separate function Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 67b605c..4b97e64 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -78,6 +78,23 @@ usage(char * const argv[]) basename(argv[0])); } +static long long +parse_int(const char * const s, long long min, long long max, + const char * const what) +{ + char *endptr = NULL; + long long ret; + + ret = strtoll(s, &endptr, 0); + if (endptr == NULL || *endptr != '\0') + errx(EXIT_FAILURE, "failed to parse %s as a number: \"%s\"", what, s); + if (ret < min || ret > max) + errx(EXIT_FAILURE, "provided value of %s is out of the expected" + " %lld..%lld range: %lld", what, min, max, ret); + + return ret; +} + int main(int argc, char *argv[]) { int i; @@ -113,9 +130,7 @@ int main(int argc, char *argv[]) if (argv[optind] == NULL) errx(EXIT_FAILURE, "threadcount is missing"); - threadcount = atoi(argv[optind]); - if (threadcount < 1) - errx(EXIT_FAILURE, "threadcount must be > 0"); + threadcount = parse_int(argv[optind], 1, INT_MAX, "threadcount"); store = X509_STORE_new(); if (store == NULL || !X509_STORE_set_default_paths(store)) From a76d93b16242e1021825802eb89296b184ababe8 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 14:36:06 +0200 Subject: [PATCH 07/23] x509storeissuer: provide the initial infrastructure for nonce configuration Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 170 +++++++++++++++++++++++++++++++++------ 1 file changed, 146 insertions(+), 24 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 4b97e64..80711e1 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -7,8 +7,9 @@ * https://www.openssl.org/source/license.html */ -#include +#include #include +#include #include #ifndef _WIN32 # include @@ -24,23 +25,118 @@ #include "perflib/perflib.h" #define RUN_TIME 5 +#define NONCE_CFG "file:servercert.pem" + +enum nonce_type { + NONCE_PATH, +}; + +struct nonce_cfg { + enum nonce_type type; + const char *path; + char **dirs; + size_t num_dirs; +}; static int error = 0; static X509_STORE *store = NULL; -static X509 *x509 = NULL; +static X509 *x509_nonce = NULL; static int threadcount; size_t *counts; OSSL_TIME max_time; +static X509 * +load_cert_from_file(const char *path) +{ + BIO *bio = BIO_new_file(path, "rb"); + X509 *x509 = NULL; + + if (bio == NULL) { + warnx("Unable to create BIO for reading \"%s\"", path); + return NULL; + } + + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (x509 == NULL) { + if (verbosity >= VERBOSITY_DEBUG) + warnx("Failed to read certificate \"%s\"", path); + } + + BIO_free(bio); + + return x509; +} + +static bool +is_abs_path(const char *path) +{ + if (path == NULL) + return false; + +#if defined(_WIN32) + /* + * So, we don't try to concatenate the provided path with the directory + * paths if the path start with the following: + * - volume character and a colon ("C:"): it is either absolute path + * (if followed by a backslash), or a relative path to a current + * directory of that volume (and we don't want to implement any logic + * that handles that); + * - backslash ("\"): it is an "absolute path" on the "current" drive, + * or (if there are two backslashes in the beginning) an UNC path. + */ + return (isalpha(path[0]) && path[1] == ':') || path[0] == '\\'; +#else /* !_WIN32 */ + return path[0] == '/'; +#endif +} + +static X509 * +load_nonce_from_path(struct nonce_cfg *cfg) +{ + if (is_abs_path(cfg->path)) + return load_cert_from_file(cfg->path); + + for (size_t i = 0; i < cfg->num_dirs; i++) { + char *cert; + X509 *ret; + + cert = perflib_mk_file_path(cfg->dirs[i], cfg->path); + if (cert == NULL) { + warnx("Failed to allocate file path for directory \"%s\"" + " and path \"%s\"", cfg->dirs[i], cfg->path); + continue; + } + + ret = load_cert_from_file(cert); + OPENSSL_free(cert); + + if (ret != NULL) + return ret; + } + + return NULL; +} + +static X509 * +make_nonce(struct nonce_cfg *cfg) +{ + switch (cfg->type) { + case NONCE_PATH: + return load_nonce_from_path(cfg); + default: + errx(EXIT_FAILURE, "Unknown nonce type: %lld", (long long) cfg->type); + } +} + static void do_x509storeissuer(size_t num) { X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; OSSL_TIME time; - if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509, NULL)) { + if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509_nonce, NULL)) { warnx("Failed to initialise X509_STORE_CTX"); error = 1; goto err; @@ -54,7 +150,7 @@ static void do_x509storeissuer(size_t num) * certificates inside our store. We're just testing calling this * against an empty store. */ - if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509) != 0) { + if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509_nonce) != 0) { warnx("Unexpected result from X509_STORE_CTX_get1_issuer"); error = 1; X509_free(issuer); @@ -73,9 +169,32 @@ static void usage(char * const argv[]) { fprintf(stderr, - "Usage: %s [-t] certsdir threadcount\n" - "-t - terse output\n", - basename(argv[0])); + "Usage: %s [-t] [-n nonce_type:type_args] certsdir threadcount\n" + "\t-t\tTerse output\n" + "\t-n\tNonce configuration, supported options:\n" + "\t\t\tfile:PATH - load nonce certificate from PATH;\n" + "\t\t\tif PATH is relative, the provided certsdir's are searched.\n" + "\t\tDefault: " NONCE_CFG "\n" + , basename(argv[0])); +} + +/** + * Parse nonce configuration string. Currently supported formats: + * * "file:PATH" - where PATH is either a relative path (that will be then + * checked against the list of directories provided), + * or an absolute one. + */ +static void +parse_nonce_cfg(const char * const optarg, struct nonce_cfg *cfg) +{ + static const char file_pfx[] = "file:"; + + if (strncmp(optarg, file_pfx, sizeof(file_pfx) - 1) == 0) { + cfg->type = NONCE_PATH; + cfg->path = optarg + sizeof(file_pfx) - 1; + } else { + errx(EXIT_FAILURE, "incorrect nonce configuration: \"%s\"", optarg); + } } static long long @@ -106,12 +225,19 @@ int main(int argc, char *argv[]) int ret = EXIT_FAILURE; BIO *bio = NULL; int opt; + int dirs_start; + struct nonce_cfg nonce_cfg; - while ((opt = getopt(argc, argv, "t")) != -1) { + parse_nonce_cfg(NONCE_CFG, &nonce_cfg); + + while ((opt = getopt(argc, argv, "tn:")) != -1) { switch (opt) { case 't': terse = 1; break; + case 'n': /* nonce */ + parse_nonce_cfg(optarg, &nonce_cfg); + break; default: usage(argv); return EXIT_FAILURE; @@ -121,11 +247,14 @@ int main(int argc, char *argv[]) if (argv[optind] == NULL) errx(EXIT_FAILURE, "certsdir is missing"); - cert = perflib_mk_file_path(argv[optind], "servercert.pem"); - if (cert == NULL) - errx(EXIT_FAILURE, "Failed to allocate cert path"); + dirs_start = optind++; - optind++; + /* + * Store the part of argv containing directories to nonce_cfg so + * load_nonce_from_path can use it later. + */ + nonce_cfg.dirs = argv + dirs_start; + nonce_cfg.num_dirs = 1; if (argv[optind] == NULL) errx(EXIT_FAILURE, "threadcount is missing"); @@ -136,21 +265,14 @@ int main(int argc, char *argv[]) if (store == NULL || !X509_STORE_set_default_paths(store)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); - bio = BIO_new_file(cert, "rb"); - if (bio == NULL) - errx(EXIT_FAILURE, "Unable to load certificate\n"); - - x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); - if (x509 == NULL) - errx(EXIT_FAILURE, "Failed to read certificate"); - - BIO_free(bio); - bio = NULL; - counts = OPENSSL_malloc(sizeof(size_t) * threadcount); if (counts == NULL) errx(EXIT_FAILURE, "Failed to create counts array"); + x509_nonce = make_nonce(&nonce_cfg); + if (x509_nonce == NULL) + errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); + max_time = ossl_time_add(ossl_time_now(), ossl_seconds2time(RUN_TIME)); if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) @@ -173,8 +295,8 @@ int main(int argc, char *argv[]) ret = EXIT_SUCCESS; err: + X509_free(x509_nonce); X509_STORE_free(store); - X509_free(x509); BIO_free(bio); OPENSSL_free(cert); OPENSSL_free(counts); From 7e323415bb0106c821168c76f39affbed919ab2f Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 16:16:19 +0200 Subject: [PATCH 08/23] x509storeissuer: add ability to configure verbosity level Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 80711e1..4ca1a73 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -27,6 +27,16 @@ #define RUN_TIME 5 #define NONCE_CFG "file:servercert.pem" +enum verbosity { + VERBOSITY_TERSE, + VERBOSITY_DEFAULT, + VERBOSITY_VERBOSE, + VERBOSITY_DEBUG_STATS, + VERBOSITY_DEBUG, + + VERBOSITY_MAX__ +}; + enum nonce_type { NONCE_PATH, }; @@ -39,6 +49,7 @@ struct nonce_cfg { }; static int error = 0; +static int verbosity = VERBOSITY_DEFAULT; static X509_STORE *store = NULL; static X509 *x509_nonce = NULL; @@ -169,8 +180,9 @@ static void usage(char * const argv[]) { fprintf(stderr, - "Usage: %s [-t] [-n nonce_type:type_args] certsdir threadcount\n" + "Usage: %s [-t] [-v] [-n nonce_type:type_args] certsdir threadcount\n" "\t-t\tTerse output\n" + "\t-v\tVerbose output. Multiple usage increases verbosity.\n" "\t-n\tNonce configuration, supported options:\n" "\t\t\tfile:PATH - load nonce certificate from PATH;\n" "\t\t\tif PATH is relative, the provided certsdir's are searched.\n" @@ -220,7 +232,6 @@ int main(int argc, char *argv[]) OSSL_TIME duration; size_t total_count = 0; double avcalltime; - int terse = 0; char *cert = NULL; int ret = EXIT_FAILURE; BIO *bio = NULL; @@ -230,10 +241,18 @@ int main(int argc, char *argv[]) parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tn:")) != -1) { + while ((opt = getopt(argc, argv, "tvn:")) != -1) { switch (opt) { - case 't': - terse = 1; + case 't': /* terse */ + verbosity = VERBOSITY_TERSE; + break; + case 'v': /* verbose */ + if (verbosity < VERBOSITY_VERBOSE) { + verbosity = VERBOSITY_VERBOSE; + } else { + if (verbosity < VERBOSITY_MAX__ - 1) + verbosity++; + } break; case 'n': /* nonce */ parse_nonce_cfg(optarg, &nonce_cfg); @@ -286,11 +305,14 @@ int main(int argc, char *argv[]) avcalltime = (double)RUN_TIME * 1e6 * threadcount / total_count; - if (terse) + switch (verbosity) { + case VERBOSITY_TERSE: printf("%lf\n", avcalltime); - else + break; + default: printf("Average time per X509_STORE_CTX_get1_issuer() call: %lfus\n", avcalltime); + } ret = EXIT_SUCCESS; From 6b9361a39e81ca6f67bce882075a849d428eec87 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 16:45:18 +0200 Subject: [PATCH 09/23] x509storeissuer: unify funtion definition formatting Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 4ca1a73..9689a4c 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -141,7 +141,8 @@ make_nonce(struct nonce_cfg *cfg) } } -static void do_x509storeissuer(size_t num) +static void +do_x509storeissuer(size_t num) { X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; @@ -226,7 +227,8 @@ parse_int(const char * const s, long long min, long long max, return ret; } -int main(int argc, char *argv[]) +int +main(int argc, char *argv[]) { int i; OSSL_TIME duration; From 3924835ccd362bfece722ebdb86bfcc0fdfe80af Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 16:45:46 +0200 Subject: [PATCH 10/23] x509storeissuer: add -T option to specify the run time Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 9689a4c..5ad0517 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -27,6 +27,8 @@ #define RUN_TIME 5 #define NONCE_CFG "file:servercert.pem" +static size_t timeout_us = RUN_TIME * 1000000; + enum verbosity { VERBOSITY_TERSE, VERBOSITY_DEFAULT, @@ -181,9 +183,13 @@ static void usage(char * const argv[]) { fprintf(stderr, - "Usage: %s [-t] [-v] [-n nonce_type:type_args] certsdir threadcount\n" + "Usage: %s [-t] [-v] [-T time] [-n nonce_type:type_args] " + "certsdir threadcount\n" "\t-t\tTerse output\n" "\t-v\tVerbose output. Multiple usage increases verbosity.\n" + "\t-T\tTimeout for the test run in seconds,\n" + "\t\tcan be fractional. Default: " + OPENSSL_MSTR(RUN_TIME) "\n" "\t-n\tNonce configuration, supported options:\n" "\t\t\tfile:PATH - load nonce certificate from PATH;\n" "\t\t\tif PATH is relative, the provided certsdir's are searched.\n" @@ -191,6 +197,23 @@ usage(char * const argv[]) , basename(argv[0])); } +static size_t +parse_timeout(const char * const optarg) +{ + char *endptr = NULL; + double timeout_s; + + timeout_s = strtod(optarg, &endptr); + + if (endptr == NULL || *endptr != '\0' || timeout_s < 0) + errx(EXIT_FAILURE, "incorrect timeout value: \"%s\""); + + if (timeout_s > SIZE_MAX / 1000000) + errx(EXIT_FAILURE, "timeout is too large: %f", timeout_s); + + return (size_t)(timeout_s * 1e6); +} + /** * Parse nonce configuration string. Currently supported formats: * * "file:PATH" - where PATH is either a relative path (that will be then @@ -243,7 +266,7 @@ main(int argc, char *argv[]) parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvn:")) != -1) { + while ((opt = getopt(argc, argv, "tvT:n:")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -256,6 +279,9 @@ main(int argc, char *argv[]) verbosity++; } break; + case 'T': /* timeout */ + timeout_us = parse_timeout(optarg); + break; case 'n': /* nonce */ parse_nonce_cfg(optarg, &nonce_cfg); break; @@ -294,7 +320,7 @@ main(int argc, char *argv[]) if (x509_nonce == NULL) errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); - max_time = ossl_time_add(ossl_time_now(), ossl_seconds2time(RUN_TIME)); + max_time = ossl_time_add(ossl_time_now(), ossl_us2time(timeout_us)); if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) errx(EXIT_FAILURE, "Failed to run the test"); @@ -305,7 +331,7 @@ main(int argc, char *argv[]) for (i = 0; i < threadcount; i++) total_count += counts[i]; - avcalltime = (double)RUN_TIME * 1e6 * threadcount / total_count; + avcalltime = (double)timeout_us * threadcount / total_count; switch (verbosity) { case VERBOSITY_TERSE: From 0b3b0916b9478ea5e5af72c19ca8f8fd500b8533 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 16:52:22 +0200 Subject: [PATCH 11/23] x509storeissuer: support providing multiple certificate directories Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 5ad0517..8e66999 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -184,7 +184,7 @@ usage(char * const argv[]) { fprintf(stderr, "Usage: %s [-t] [-v] [-T time] [-n nonce_type:type_args] " - "certsdir threadcount\n" + "certsdir [certsdir...] threadcount\n" "\t-t\tTerse output\n" "\t-v\tVerbose output. Multiple usage increases verbosity.\n" "\t-T\tTimeout for the test run in seconds,\n" @@ -301,12 +301,12 @@ main(int argc, char *argv[]) * load_nonce_from_path can use it later. */ nonce_cfg.dirs = argv + dirs_start; - nonce_cfg.num_dirs = 1; + nonce_cfg.num_dirs = argc - 1 - dirs_start; - if (argv[optind] == NULL) + if (optind >= argc) errx(EXIT_FAILURE, "threadcount is missing"); - threadcount = parse_int(argv[optind], 1, INT_MAX, "threadcount"); + threadcount = parse_int(argv[argc - 1], 1, INT_MAX, "threadcount"); store = X509_STORE_new(); if (store == NULL || !X509_STORE_set_default_paths(store)) From c7f19dc256537e32a7985a8505f494747eecda3b Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 21 Oct 2025 15:43:55 +0200 Subject: [PATCH 12/23] x509storeissuer: reduce the rate ossl_time_now() queries While querying system time is usually cheap on Linux on IA-32, that might not be the case elsewhere, and that might impact the cost of the loop iteration significantly, as the measured call time is quite small (in the order of a microsecond). Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 8e66999..9a99edd 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -148,7 +148,7 @@ do_x509storeissuer(size_t num) { X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; - OSSL_TIME time; + OSSL_TIME time = ossl_time_now(); if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509_nonce, NULL)) { warnx("Failed to initialise X509_STORE_CTX"); @@ -172,7 +172,8 @@ do_x509storeissuer(size_t num) } issuer = NULL; counts[num]++; - time = ossl_time_now(); + if ((count & 0x3f) == 0) + time = ossl_time_now(); } while (time.t < max_time.t); err: From effd1b71c6c4ee6fd0ef85d52dcb6a944a212c65 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 21 Oct 2025 15:49:35 +0200 Subject: [PATCH 13/23] x509storeissuer: count the iterations in a local variable to avoid hammering the common counts array's cache line Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 9a99edd..fe7accc 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -149,6 +149,7 @@ do_x509storeissuer(size_t num) X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; OSSL_TIME time = ossl_time_now(); + size_t count = 0; if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509_nonce, NULL)) { warnx("Failed to initialise X509_STORE_CTX"); @@ -156,8 +157,6 @@ do_x509storeissuer(size_t num) goto err; } - counts[num] = 0; - do { /* * We actually expect this to fail. We've not configured any @@ -171,13 +170,15 @@ do_x509storeissuer(size_t num) goto err; } issuer = NULL; - counts[num]++; + count++; if ((count & 0x3f) == 0) time = ossl_time_now(); } while (time.t < max_time.t); err: X509_STORE_CTX_free(ctx); + + counts[num] = count; } static void From a4c199d783dcc14501e8de3176f8018cec60505a Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 21 Oct 2025 16:06:16 +0200 Subject: [PATCH 14/23] x509storeissuer: tolerate X509_STORE_CTX_get1_issuer() successes, count and report successful calls Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index fe7accc..7b9092f 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -58,6 +58,7 @@ static X509 *x509_nonce = NULL; static int threadcount; size_t *counts; +size_t *founds; OSSL_TIME max_time; static X509 * @@ -150,6 +151,7 @@ do_x509storeissuer(size_t num) X509 *issuer = NULL; OSSL_TIME time = ossl_time_now(); size_t count = 0; + size_t found = 0; if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509_nonce, NULL)) { warnx("Failed to initialise X509_STORE_CTX"); @@ -158,16 +160,9 @@ do_x509storeissuer(size_t num) } do { - /* - * We actually expect this to fail. We've not configured any - * certificates inside our store. We're just testing calling this - * against an empty store. - */ if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509_nonce) != 0) { - warnx("Unexpected result from X509_STORE_CTX_get1_issuer"); - error = 1; + found++; X509_free(issuer); - goto err; } issuer = NULL; count++; @@ -179,6 +174,7 @@ do_x509storeissuer(size_t num) X509_STORE_CTX_free(ctx); counts[num] = count; + founds[num] = found; } static void @@ -258,6 +254,7 @@ main(int argc, char *argv[]) int i; OSSL_TIME duration; size_t total_count = 0; + size_t total_found = 0; double avcalltime; char *cert = NULL; int ret = EXIT_FAILURE; @@ -318,6 +315,10 @@ main(int argc, char *argv[]) if (counts == NULL) errx(EXIT_FAILURE, "Failed to create counts array"); + founds = OPENSSL_malloc(sizeof(size_t) * threadcount); + if (founds == NULL) + errx(EXIT_FAILURE, "Failed to create founds array"); + x509_nonce = make_nonce(&nonce_cfg); if (x509_nonce == NULL) errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); @@ -330,8 +331,10 @@ main(int argc, char *argv[]) if (error) errx(EXIT_FAILURE, "Error during test"); - for (i = 0; i < threadcount; i++) + for (i = 0; i < threadcount; i++) { total_count += counts[i]; + total_found += founds[i]; + } avcalltime = (double)timeout_us * threadcount / total_count; @@ -342,6 +345,12 @@ main(int argc, char *argv[]) default: printf("Average time per X509_STORE_CTX_get1_issuer() call: %lfus\n", avcalltime); + if (verbosity >= VERBOSITY_VERBOSE) { + printf("Successful X509_STORE_CTX_get1_issuer() calls: %zu of %zu" + " (%lf%%)\n", + total_found, total_count, + (double)total_found / total_count * 100.0); + } } ret = EXIT_SUCCESS; @@ -351,6 +360,7 @@ main(int argc, char *argv[]) X509_STORE_free(store); BIO_free(bio); OPENSSL_free(cert); + OPENSSL_free(founds); OPENSSL_free(counts); return ret; } From 4f491bf6aa81891e705c9c9e256802b2aa73220c Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 21 Oct 2025 16:09:37 +0200 Subject: [PATCH 15/23] x509storeissuer: add certificates from the provided directories to the store Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 154 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 4 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 7b9092f..90438ff 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -7,11 +7,14 @@ * https://www.openssl.org/source/license.html */ +#include #include #include #include #include +#include #ifndef _WIN32 +# include # include # include #else @@ -144,6 +147,146 @@ make_nonce(struct nonce_cfg *cfg) } } +static size_t +read_cert(const char * const dir, const char * const name, X509_STORE * const store) +{ + X509 *x509 = NULL; + char *path = NULL; + size_t ret = 1; + + path = perflib_mk_file_path(dir, name); + if (path == NULL) { + warn("Failed to allocate cert name in directory \"%s\" for file \"%s\"", + dir, name); + goto out; + } + + x509 = load_cert_from_file(path); + if (x509 == NULL) { + goto out; + } + + if (!X509_STORE_add_cert(store, x509)) { + warnx("Failed to add a certificate from \"%s\" to the store\n", path); + goto out; + } + + if (verbosity >= VERBOSITY_DEBUG) + fprintf(stderr, "Successfully added a certificate from \"%s\"" + " to the store\n", path); + + ret = 1; + + out: + X509_free(x509); + OPENSSL_free(path); + + return ret; +} + +#if defined(_WIN32) +static size_t +read_certsdir(char * const dir, X509_STORE * const store) +{ + const size_t dir_len = strlen(dir); + const size_t glob_len = dir_len + sizeof("\\*"); + size_t cnt = 0; + char *search_glob = NULL; + HANDLE find_handle = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA find_data; + DWORD last_err; + + search_glob = OPENSSL_malloc(glob_len); + if (search_glob == NULL) { + warnx("Error allocating a search glob for \"%s\"", dir); + return 0; + } + + if (snprintf(search_glob, glob_len, "%s\\*", dir) != glob_len - 1) { + warnx("Error generating a search glob for \"%s\"", dir); + goto out; + } + + find_handle = FindFirstFileA(search_glob, &find_data); + if (find_handle == INVALID_HANDLE_VALUE) { + warnx("Error in FindFirstFile(): %#lx", GetLastError()); + goto out; + } + + do { + if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (verbosity >= VERBOSITY_DEBUG) + warnx("\"%s\\%s\" is a directory file, skipping", + dir, find_data.cFileName); + continue; + } + + cnt += read_cert(dir, find_data.cFileName, store); + } while (FindNextFileA(find_handle, &find_data) != 0); + + last_err = GetLastError(); + if (last_err != ERROR_NO_MORE_FILES) + warnx("Error in FindNextFile(): %#lx", last_err); + + out: + if (find_handle != INVALID_HANDLE_VALUE) + FindClose(find_handle); + OPENSSL_free(search_glob); + + return cnt; +} +#else /* !defined(_WIN32) */ +static size_t +read_certsdir(char * const dir, X509_STORE * const store) +{ + struct stat st; + struct dirent *e; + DIR *d = opendir(dir); + size_t cnt = 0; + + if (d == NULL) { + warn("Could not open \"%s\"", dir); + + return 0; + } + + while (1) { + errno = 0; + e = readdir(d); + + if (e == NULL) { + if (errno != 0) + warn("An error occurred while reading directory \"%s\"", dir); + + break; + } + + if (e->d_type != DT_REG && e->d_type != DT_UNKNOWN) { + if (verbosity >= VERBOSITY_DEBUG) + warnx("\"%s/%s\" is not a regular file, skipping", + dir, e->d_name); + continue; + } + + cnt += read_cert(dir, e->d_name, store); + } + + return cnt; +} +#endif /* defined(_WIN32) */ + +static size_t +read_certsdirs(char * const * const dirs, const int dir_cnt, + X509_STORE * const store) +{ + size_t cnt = 0; + + for (int i = 0; i < dir_cnt; i++) + cnt += read_certsdir(dirs[i], store); + + return cnt; +} + static void do_x509storeissuer(size_t num) { @@ -256,11 +399,10 @@ main(int argc, char *argv[]) size_t total_count = 0; size_t total_found = 0; double avcalltime; - char *cert = NULL; int ret = EXIT_FAILURE; - BIO *bio = NULL; int opt; int dirs_start; + size_t num_certs = 0; struct nonce_cfg nonce_cfg; parse_nonce_cfg(NONCE_CFG, &nonce_cfg); @@ -311,6 +453,12 @@ main(int argc, char *argv[]) if (store == NULL || !X509_STORE_set_default_paths(store)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); + num_certs += read_certsdirs(argv + dirs_start, argc - dirs_start - 1, + store); + + if (verbosity >= VERBOSITY_DEBUG_STATS) + fprintf(stderr, "Added %zu certificates to the store\n", num_certs); + counts = OPENSSL_malloc(sizeof(size_t) * threadcount); if (counts == NULL) errx(EXIT_FAILURE, "Failed to create counts array"); @@ -358,8 +506,6 @@ main(int argc, char *argv[]) err: X509_free(x509_nonce); X509_STORE_free(store); - BIO_free(bio); - OPENSSL_free(cert); OPENSSL_free(founds); OPENSSL_free(counts); return ret; From fc6682dfdac789c9b68bd8ec5b9f9a57b1659513 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 21 Oct 2025 16:52:30 +0200 Subject: [PATCH 16/23] x509storeissuer: add and option to configure X509_STORE_CTX sharing Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 48 ++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 90438ff..b071183 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -29,6 +29,7 @@ #define RUN_TIME 5 #define NONCE_CFG "file:servercert.pem" +#define CTX_SHARE_THREADS 1 static size_t timeout_us = RUN_TIME * 1000000; @@ -53,6 +54,10 @@ struct nonce_cfg { size_t num_dirs; }; +struct thread_data { + X509_STORE_CTX *ctx; +} *thread_data; + static int error = 0; static int verbosity = VERBOSITY_DEFAULT; static X509_STORE *store = NULL; @@ -290,20 +295,15 @@ read_certsdirs(char * const * const dirs, const int dir_cnt, static void do_x509storeissuer(size_t num) { + struct thread_data *td = thread_data + num; X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; OSSL_TIME time = ossl_time_now(); size_t count = 0; size_t found = 0; - if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509_nonce, NULL)) { - warnx("Failed to initialise X509_STORE_CTX"); - error = 1; - goto err; - } - do { - if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509_nonce) != 0) { + if (X509_STORE_CTX_get1_issuer(&issuer, td->ctx, x509_nonce) != 0) { found++; X509_free(issuer); } @@ -325,7 +325,7 @@ usage(char * const argv[]) { fprintf(stderr, "Usage: %s [-t] [-v] [-T time] [-n nonce_type:type_args] " - "certsdir [certsdir...] threadcount\n" + "[-C threads] certsdir [certsdir...] threadcount\n" "\t-t\tTerse output\n" "\t-v\tVerbose output. Multiple usage increases verbosity.\n" "\t-T\tTimeout for the test run in seconds,\n" @@ -335,6 +335,9 @@ usage(char * const argv[]) "\t\t\tfile:PATH - load nonce certificate from PATH;\n" "\t\t\tif PATH is relative, the provided certsdir's are searched.\n" "\t\tDefault: " NONCE_CFG "\n" + "\t-C\tNumber of threads that share the same X.509\n" + "\t\tstore context object. Default: " + OPENSSL_MSTR(CTX_SHARE_THREADS) "\n" , basename(argv[0])); } @@ -396,6 +399,7 @@ main(int argc, char *argv[]) { int i; OSSL_TIME duration; + size_t ctx_share_cnt = CTX_SHARE_THREADS; size_t total_count = 0; size_t total_found = 0; double avcalltime; @@ -407,7 +411,7 @@ main(int argc, char *argv[]) parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvT:n:")) != -1) { + while ((opt = getopt(argc, argv, "tvT:n:C:")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -426,6 +430,10 @@ main(int argc, char *argv[]) case 'n': /* nonce */ parse_nonce_cfg(optarg, &nonce_cfg); break; + case 'C': /* how many threads share X509_STORE_CTX */ + ctx_share_cnt = parse_int(optarg, 1, INT_MAX, + "X509_STORE_CTX share degree"); + break; default: usage(argv); return EXIT_FAILURE; @@ -449,6 +457,10 @@ main(int argc, char *argv[]) threadcount = parse_int(argv[argc - 1], 1, INT_MAX, "threadcount"); + thread_data = OPENSSL_zalloc(threadcount * sizeof(*thread_data)); + if (thread_data == NULL) + errx(EXIT_FAILURE, "Failed to create thread_data array"); + store = X509_STORE_new(); if (store == NULL || !X509_STORE_set_default_paths(store)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); @@ -471,6 +483,19 @@ main(int argc, char *argv[]) if (x509_nonce == NULL) errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); + for (size_t i = 0; i < threadcount; i++) { + if (i % ctx_share_cnt) { + thread_data[i].ctx = thread_data[i - i % ctx_share_cnt].ctx; + } else { + thread_data[i].ctx = X509_STORE_CTX_new(); + if (thread_data[i].ctx == NULL + || !X509_STORE_CTX_init(thread_data[i].ctx, store, x509_nonce, + NULL)) + errx(EXIT_FAILURE, "Failed to initialise X509_STORE_CTX" + " for thread %zu", i); + } + } + max_time = ossl_time_add(ossl_time_now(), ossl_us2time(timeout_us)); if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) @@ -508,5 +533,10 @@ main(int argc, char *argv[]) X509_STORE_free(store); OPENSSL_free(founds); OPENSSL_free(counts); + if (thread_data != NULL) { + for (size_t i = 0; i < threadcount; i += ctx_share_cnt) + X509_STORE_CTX_free(thread_data[i].ctx); + } + OPENSSL_free(thread_data); return ret; } From 5724e00424fce35e18fdff0e14323b040641a9ab Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Wed, 22 Oct 2025 14:55:57 +0200 Subject: [PATCH 17/23] x509storeissuer: report the store size before the test run if the verbosity level is DEBUG_STATS or higher Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index b071183..0bc3a08 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -320,6 +320,27 @@ do_x509storeissuer(size_t num) founds[num] = found; } +static void +report_store_size(X509_STORE * const store, const char * const suffix, + int verbosity) +{ + if (verbosity >= VERBOSITY_DEBUG_STATS) { + STACK_OF(X509_OBJECT) *sk = +#if OPENSSL_VERSION_NUMBER >= 0x30300000L + X509_STORE_get1_objects(store); +#else + X509_STORE_get0_objects(store); +#endif + + fprintf(stderr, "Number of certificates in the store %s: %d\n", + suffix, sk_X509_OBJECT_num(sk)); + +#if OPENSSL_VERSION_NUMBER >= 0x30300000L + sk_X509_OBJECT_pop_free(sk, X509_OBJECT_free); +#endif + } +} + static void usage(char * const argv[]) { @@ -479,6 +500,8 @@ main(int argc, char *argv[]) if (founds == NULL) errx(EXIT_FAILURE, "Failed to create founds array"); + report_store_size(store, "before the test run", verbosity); + x509_nonce = make_nonce(&nonce_cfg); if (x509_nonce == NULL) errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); From a5d0c9e47fc90d8ff2caba0e3cc74d47fc0596fb Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Wed, 22 Oct 2025 16:13:30 +0200 Subject: [PATCH 18/23] x509storeissuer: make the verbose reporting more elaborate Signed-off-by: Eugene Syromiatnikov --- source/CMakeLists.txt | 14 ++- source/x509storeissuer.c | 248 +++++++++++++++++++++++++++++++-------- 2 files changed, 209 insertions(+), 53 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index b46f21a..1f0290f 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -117,6 +117,12 @@ if(WIN32) target_sources(perf PRIVATE perflib/getopt.c perflib/basename.c perflib/err.c) endif() +if(WIN32) + set(libm_dep) +else() + set(libm_dep PUBLIC m) +endif() + target_include_directories(perf PUBLIC "${PROJECT_SOURCE_DIR}") target_link_libraries(perf PUBLIC OpenSSL::SSL OpenSSL::Crypto) @@ -154,17 +160,13 @@ add_executable(rsasign rsasign.c) target_link_libraries(rsasign PRIVATE perf) add_executable(x509storeissuer x509storeissuer.c) -target_link_libraries(x509storeissuer PRIVATE perf) +target_link_libraries(x509storeissuer ${libm_dep} PRIVATE perf) add_executable(rwlocks rwlocks.c) target_link_libraries(rwlocks PRIVATE perf) add_executable(pkeyread pkeyread.c) -if(WIN32) - target_link_libraries(pkeyread PRIVATE perf) -else() - target_link_libraries(pkeyread PUBLIC m PRIVATE perf) -endif() +target_link_libraries(pkeyread ${libm_dep} PRIVATE perf) add_executable(evp_setpeer evp_setpeer.c) target_link_libraries(evp_setpeer PRIVATE perf) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 0bc3a08..f48dfdf 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -28,10 +29,12 @@ #include "perflib/perflib.h" #define RUN_TIME 5 +#define QUANTILES 5 #define NONCE_CFG "file:servercert.pem" #define CTX_SHARE_THREADS 1 static size_t timeout_us = RUN_TIME * 1000000; +static size_t quantiles = QUANTILES; enum verbosity { VERBOSITY_TERSE, @@ -47,6 +50,21 @@ enum nonce_type { NONCE_PATH, }; +struct call_times { + uint64_t duration; + uint64_t total_count; + uint64_t total_found; + uint64_t min_count; + uint64_t max_count; + double avg; + double min; + double max; + double stddev; + double median; + size_t min_idx; + size_t max_idx; +}; + struct nonce_cfg { enum nonce_type type; const char *path; @@ -55,6 +73,12 @@ struct nonce_cfg { }; struct thread_data { + OSSL_TIME start_time; + struct { + uint64_t count; + uint64_t found; + OSSL_TIME end_time; + } *q_data; X509_STORE_CTX *ctx; } *thread_data; @@ -65,10 +89,11 @@ static X509 *x509_nonce = NULL; static int threadcount; -size_t *counts; -size_t *founds; OSSL_TIME max_time; +#define OSSL_MIN(p, q) ((p) < (q) ? (p) : (q)) +#define OSSL_MAX(p, q) ((p) > (q) ? (p) : (q)) + static X509 * load_cert_from_file(const char *path) { @@ -296,12 +321,18 @@ static void do_x509storeissuer(size_t num) { struct thread_data *td = thread_data + num; - X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; OSSL_TIME time = ossl_time_now(); + OSSL_TIME duration; + OSSL_TIME q_end; + size_t q = 0; size_t count = 0; size_t found = 0; + td->start_time = ossl_time_now(); + duration.t = max_time.t - td->start_time.t; + q_end.t = duration.t / quantiles + td->start_time.t; + do { if (X509_STORE_CTX_get1_issuer(&issuer, td->ctx, x509_nonce) != 0) { found++; @@ -309,15 +340,20 @@ do_x509storeissuer(size_t num) } issuer = NULL; count++; - if ((count & 0x3f) == 0) + if ((count & 0x3f) == 0) { time = ossl_time_now(); + if (time.t >= q_end.t) { + td->q_data[q].count = count; + td->q_data[q].found = found; + td->q_data[q].end_time = time; + q_end.t = (duration.t * (++q + 1)) / quantiles + td->start_time.t; + } + } } while (time.t < max_time.t); - err: - X509_STORE_CTX_free(ctx); - - counts[num] = count; - founds[num] = found; + td->q_data[quantiles - 1].count = count; + td->q_data[quantiles - 1].found = found; + td->q_data[quantiles - 1].end_time = time; } static void @@ -341,14 +377,147 @@ report_store_size(X509_STORE * const store, const char * const suffix, } } +static int +cmp_double(const void *a_ptr, const void *b_ptr) +{ + const double * const a = a_ptr; + const double * const b = b_ptr; + + return *a - *b < 0 ? -1 : *a - *b > 0 ? 1 : 0; +} + +static void +get_calltimes(struct call_times *times, int verbosity) +{ + double *call_times; + + for (size_t q = 0; q < quantiles; q++) { + for (size_t i = 0; i < threadcount; i++) { + uint64_t start_t = q ? thread_data[i].q_data[q - 1].end_time.t + : thread_data[i].start_time.t; + uint64_t count = thread_data[i].q_data[q].count - + (q ? thread_data[i].q_data[q - 1].count : 0); + uint64_t found = thread_data[i].q_data[q].found - + (q ? thread_data[i].q_data[q - 1].found : 0); + + times[q].duration += thread_data[i].q_data[q].end_time.t - start_t; + times[q].total_count += count; + times[q].total_found += found; + } + } + + for (size_t q = 0; q < quantiles; q++) { + times[quantiles].duration += times[q].duration; + times[quantiles].total_count += times[q].total_count; + times[quantiles].total_found += times[q].total_found; + } + + for (size_t q = (quantiles == 1); q <= quantiles; q++) + times[q].avg = (double) times[q].duration / OSSL_TIME_US / times[q].total_count; + + if (verbosity >= VERBOSITY_VERBOSE) { + call_times = OPENSSL_zalloc(threadcount * sizeof(*call_times)); + + for (size_t q = (quantiles == 1); q <= quantiles; q++) { + double variance = 0; + + for (size_t i = 0; i < threadcount; i++) { + uint64_t start_t = q && q != quantiles + ? thread_data[i].q_data[q - 1].end_time.t + : thread_data[i].start_time.t; + uint64_t duration = + thread_data[i].q_data[OSSL_MIN(q, quantiles - 1)].end_time.t + - start_t; + uint64_t count = + thread_data[i].q_data[OSSL_MIN(q, quantiles - 1)].count - + (q && q != quantiles ? thread_data[i].q_data[q - 1].count + : 0); + call_times[i] = (double) duration / OSSL_TIME_US / count; + } + + times[q].min = times[q].max = call_times[0]; + times[q].min_idx = times[q].max_idx = 0; + + for (size_t i = 0; i < threadcount; i++) { + if (call_times[i] < times[q].min) { + times[q].min = call_times[i]; + times[q].min_idx = i; + } + + if (call_times[i] > times[q].max) { + times[q].max = call_times[i]; + times[q].max_idx = i; + } + } + + qsort(call_times, threadcount, sizeof(call_times[0]), cmp_double); + times[q].median = call_times[threadcount / 2]; + + for (size_t i = 0; i < threadcount; i++) { + double dev = call_times[i] - times[q].avg; + + variance += dev * dev; + } + + times[q].stddev = sqrt(variance / threadcount); + } + + OPENSSL_free(call_times); + } +} + +static void +report_result(int verbosity) +{ + struct call_times *times; + + times = OPENSSL_zalloc(sizeof(*times) * (quantiles + 1)); + + get_calltimes(times, verbosity); + + switch (verbosity) { + case VERBOSITY_TERSE: + printf("%lf\n", times[1].avg); + break; + case VERBOSITY_DEFAULT: + printf("Average time per call: %lfus\n", times[1].avg); + break; + case VERBOSITY_VERBOSE: + default: + /* if quantiles == 1, we only need to print total runtime info */ + for (size_t i = (quantiles == 1); i <= quantiles; i++) { + if (i < quantiles) + printf("Part %8zu", i + 1); + else + printf("Total runtime"); + + printf(": avg: %9.3lf us, median: %9.3lf us" + ", min: %9.3lf us @thread %3zu, max: %9.3lf us @thread %3zu" + ", stddev: %9.3lf us (%8.4lf%%)" + ", hits %9zu of %9zu (%8.4lf%%)\n", + times[i].avg, times[i].median, + times[i].min, times[i].min_idx, + times[i].max, times[i].max_idx, + times[i].stddev, + 100.0 * times[i].stddev / times[i].avg, + times[i].total_found, times[i].total_count, + 100.0 * times[i].total_found / (times[i].total_count)); + } + break; + } +} + static void usage(char * const argv[]) { fprintf(stderr, - "Usage: %s [-t] [-v] [-T time] [-n nonce_type:type_args] " + "Usage: %s [-t] [-v] [-q N] [-T time] [-n nonce_type:type_args] " "[-C threads] certsdir [certsdir...] threadcount\n" "\t-t\tTerse output\n" "\t-v\tVerbose output. Multiple usage increases verbosity.\n" + "\t-q\tGather information about temporal N-quantiles.\n" + "\t\tDone only when the output is verbose. Default: " + OPENSSL_MSTR(QUANTILES) "\n" "\t-T\tTimeout for the test run in seconds,\n" "\t\tcan be fractional. Default: " OPENSSL_MSTR(RUN_TIME) "\n" @@ -421,9 +590,6 @@ main(int argc, char *argv[]) int i; OSSL_TIME duration; size_t ctx_share_cnt = CTX_SHARE_THREADS; - size_t total_count = 0; - size_t total_found = 0; - double avcalltime; int ret = EXIT_FAILURE; int opt; int dirs_start; @@ -432,7 +598,7 @@ main(int argc, char *argv[]) parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvT:n:C:")) != -1) { + while ((opt = getopt(argc, argv, "tvq:T:n:C:")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -445,6 +611,10 @@ main(int argc, char *argv[]) verbosity++; } break; + case 'q': /* quantiles */ + quantiles = parse_int(optarg, 1, INT_MAX, + "number of quantiles"); + break; case 'T': /* timeout */ timeout_us = parse_timeout(optarg); break; @@ -461,6 +631,9 @@ main(int argc, char *argv[]) } } + if (verbosity < VERBOSITY_VERBOSE) + quantiles = 1; + if (argv[optind] == NULL) errx(EXIT_FAILURE, "certsdir is missing"); @@ -482,6 +655,14 @@ main(int argc, char *argv[]) if (thread_data == NULL) errx(EXIT_FAILURE, "Failed to create thread_data array"); + for (size_t i = 0; i < threadcount; i++) { + thread_data[i].q_data = OPENSSL_zalloc(quantiles * + sizeof(*(thread_data[i].q_data))); + if (thread_data[i].q_data == NULL) + errx(EXIT_FAILURE, "Failed to create quantiles array for thread" + " %zu", i); + } + store = X509_STORE_new(); if (store == NULL || !X509_STORE_set_default_paths(store)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); @@ -492,14 +673,6 @@ main(int argc, char *argv[]) if (verbosity >= VERBOSITY_DEBUG_STATS) fprintf(stderr, "Added %zu certificates to the store\n", num_certs); - counts = OPENSSL_malloc(sizeof(size_t) * threadcount); - if (counts == NULL) - errx(EXIT_FAILURE, "Failed to create counts array"); - - founds = OPENSSL_malloc(sizeof(size_t) * threadcount); - if (founds == NULL) - errx(EXIT_FAILURE, "Failed to create founds array"); - report_store_size(store, "before the test run", verbosity); x509_nonce = make_nonce(&nonce_cfg); @@ -527,38 +700,19 @@ main(int argc, char *argv[]) if (error) errx(EXIT_FAILURE, "Error during test"); - for (i = 0; i < threadcount; i++) { - total_count += counts[i]; - total_found += founds[i]; - } - - avcalltime = (double)timeout_us * threadcount / total_count; - - switch (verbosity) { - case VERBOSITY_TERSE: - printf("%lf\n", avcalltime); - break; - default: - printf("Average time per X509_STORE_CTX_get1_issuer() call: %lfus\n", - avcalltime); - if (verbosity >= VERBOSITY_VERBOSE) { - printf("Successful X509_STORE_CTX_get1_issuer() calls: %zu of %zu" - " (%lf%%)\n", - total_found, total_count, - (double)total_found / total_count * 100.0); - } - } + report_result(verbosity); ret = EXIT_SUCCESS; err: X509_free(x509_nonce); X509_STORE_free(store); - OPENSSL_free(founds); - OPENSSL_free(counts); if (thread_data != NULL) { - for (size_t i = 0; i < threadcount; i += ctx_share_cnt) - X509_STORE_CTX_free(thread_data[i].ctx); + for (size_t i = 0; i < threadcount; i++) { + if (!(i % ctx_share_cnt)) + X509_STORE_CTX_free(thread_data[i].ctx); + OPENSSL_free(thread_data[i].q_data); + } } OPENSSL_free(thread_data); return ret; From 2282242b016b92715fc78b541d266018c0b0d9d6 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Wed, 22 Oct 2025 16:28:50 +0200 Subject: [PATCH 19/23] x509storeissuer: generate some certificates and add them to the store Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 476 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 473 insertions(+), 3 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index f48dfdf..83679e0 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -24,10 +24,16 @@ # include "perflib/basename.h" #endif /* _WIN32 */ #include +#include +#include #include #include "perflib/err.h" #include "perflib/perflib.h" +#define NUM_CERTS 1024 +#define NUM_LOAD_CERTS 128 +#define NUM_KEYS 16 +#define KEY_ALGO "rsa:2048" #define RUN_TIME 5 #define QUANTILES 5 #define NONCE_CFG "file:servercert.pem" @@ -94,6 +100,363 @@ OSSL_TIME max_time; #define OSSL_MIN(p, q) ((p) < (q) ? (p) : (q)) #define OSSL_MAX(p, q) ((p) > (q) ? (p) : (q)) +static EVP_PKEY_CTX * +make_pkey_ctx(const char *algstr, char **alg_storage) +{ + EVP_PKEY_CTX *ctx = NULL; + const char *alg; + const char *p = strchr(algstr, ':'); + + alg = p ? *alg_storage = OPENSSL_strndup(algstr, p - algstr) : algstr; + if (alg == NULL) { + warnx("Error getting algorithm name from \"%s\"", algstr); + goto err; + } + + ctx = EVP_PKEY_CTX_new_from_name(NULL, alg, NULL); + if (ctx == NULL) { + warnx("Error allocating keygen context"); + goto err; + } + + if (EVP_PKEY_keygen_init(ctx) <= 0) { + warnx("Error initialising keygen contextx"); + goto err; + } + + /* The bits part */ + if (p && p[1] >= '0' && p[1] <= '9') { + char *endptr = NULL; + long bits = strtol(p + 1, &endptr, 0); + if (!endptr || (*endptr != '\0' && *endptr != ':')) { + warnx("Error while parsing bits from \"%s\"", p + 1); + goto err; + } + p = endptr; + + if (bits > 0) { + OSSL_PARAM params[] = { OSSL_PARAM_END, OSSL_PARAM_END }; + size_t val = bits; + + params[0] = OSSL_PARAM_construct_size_t(OSSL_PKEY_PARAM_BITS, &val); + + if (EVP_PKEY_CTX_set_params(ctx, params) <= 0) { + warnx("Error setting bits value of %zu to algorithm \"%s\"", + val, alg); + goto err; + } + } + } + + /* param=value pairs */ + while (p && p[0] == ':' && p[1] != '\0') { + const char *param_str = p + 1; + char *param; + char *val; + const char *eq; + int ret; + + eq = strchr(param_str, '='); + p = strchr(param_str, ':'); + + if (eq == NULL || (p != NULL && eq > p)) { + warnx("Error while parsing a param=value pair from \"%s\"", param_str); + goto err; + } + + param = OPENSSL_strndup(param_str, eq - param_str); + if (param == NULL) { + warnx("Error allocating param name string\n"); + goto err; + } + + if (p) + val = OPENSSL_strndup(eq + 1, p - eq - 1); + else + val = OPENSSL_strdup(eq + 1); + if (val == NULL) { + warnx("Error allocating param value string"); + OPENSSL_free(param); + goto err; + } + + if ((ret = EVP_PKEY_CTX_ctrl_str(ctx, param, val)) <= 0) { + warnx("Error setting parameter \"%s\" to value \"%s\" for algorithm" + " \"%s\", got %d\n", param, val, alg, ret); + OPENSSL_free(val); + OPENSSL_free(param); + goto err; + } + + OPENSSL_free(val); + OPENSSL_free(param); + } + + return ctx; + +err: + EVP_PKEY_CTX_free(ctx); + + return NULL; +} + +static EVP_PKEY * +gen_key(EVP_PKEY_CTX *ctx) +{ + EVP_PKEY *res = NULL; + + if (EVP_PKEY_keygen(ctx, &res) <= 0) { + warnx("Error generating key"); + + return NULL; + } + + return res; +} + +static EVP_PKEY * +get_pubkey(EVP_PKEY *pkey) +{ + BIO *bio = NULL; + EVP_PKEY *pubkey = NULL; + + bio = BIO_new(BIO_s_mem()); + if (!bio) { + warnx("Error creating memory BIO"); + goto err; + } + + if (!PEM_write_bio_PUBKEY(bio, pkey)) { + warnx("Error writing public key to BIO"); + goto err; + } + + if (BIO_seek(bio, 0) < 0) { + warnx("Error resetting BIO cursor"); + goto err; + } + + pubkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (pubkey == NULL) { + warnx("Error reading pubkey from BIO"); + goto err; + } + + BIO_free(bio); + + return pubkey; + +err: + EVP_PKEY_free(pubkey); + BIO_free(bio); + + return NULL; +} + +/** + * A wrapper for populating keys and pubkeys arrays with generated keys. + * + * @param[in] num_gen_keys Number of keys to generate. + * @param[in] key_ctx Key context, created with make_pkey_ctx(). + * @param[in,out] keys Array of num_gen_keys private keys, populated + * by the function, elements should be freed + * with EVP_PKEY_free() after use. + * @param[in,out] pubkeys Array of num_gen_keys public keys, populated + * by the function, elements should be freed + * with EVP_PKEY_free() after use. + * @return true on success, false on failure. + */ +static bool +gen_keys(size_t num_gen_keys, EVP_PKEY_CTX *key_ctx, + EVP_PKEY **keys, EVP_PKEY **pubkeys) +{ + OSSL_TIME last = ossl_time_now(); + + for (size_t i = 0; i < num_gen_keys; i++) { + if (verbosity >= VERBOSITY_DEBUG_STATS) { + OSSL_TIME cur = ossl_time_now(); + + if (cur.t - last.t > OSSL_TIME_SECOND) { + fprintf(stderr, "Generating key %zu out of %zu...\n", + i + 1, num_gen_keys); + last.t = cur.t; + } + } + keys[i] = gen_key(key_ctx); + if (keys[i] == NULL) { + warnx("Generation of private key %zu of %zu failed", + i + 1, num_gen_keys); + return false; + } + + pubkeys[i] = get_pubkey(keys[i]); + if (pubkeys[i] == NULL) { + warnx("Generation of public key %zu of %zu failed", + i + 1, num_gen_keys); + return false; + } + } + + if (verbosity >= VERBOSITY_DEBUG_STATS) + fprintf(stderr, "Generated %zu keys\n", num_gen_keys); + + return true; +} + +static const unsigned char * +gen_name(void) +{ + static const char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV" + "WXYZabcdefghijklmnopqrstuvwxyz "; + static unsigned char out[64]; + size_t len; + + len = rand() % (sizeof(out) - 2) + 1; + + for (size_t i = 0; i < len; i++) + out[i] = chars[rand() % (sizeof(chars) - 1)]; + + out[len] = '\0'; + + return out; +} + +static X509 * +gen_cert(EVP_PKEY * const pkey, EVP_PKEY * const pubkey, + const unsigned char * const sn, const unsigned char * const in) +{ + X509 *cert = NULL; + X509_NAME *issuer = NULL; + X509_NAME *subject = NULL; + EVP_MD_CTX *mctx = NULL; + const unsigned char *in_str; + const unsigned char *sn_str; + int error = 1; + + cert = X509_new(); + if (!cert) { + warnx("Error creating X509 certificate object"); + goto out; + } + + issuer = X509_NAME_new(); + if (!issuer) { + warnx("Error creating X509 issuer name object"); + goto out; + } + + in_str = in ? in : gen_name(); + if (!X509_NAME_add_entry_by_txt(issuer, "CN", MBSTRING_ASC, + in_str, -1, -1, 0)) { + warnx("Error setting X509 issuer name \"%s\"", (const char *)in_str); + goto out; + } + + if (!X509_set_issuer_name(cert, issuer)) { + warnx("Error setting X509 certificate issuer name:"); + X509_NAME_print_ex_fp(stderr, issuer, 2, 0); + goto out; + } + + subject = X509_NAME_new(); + if (!subject) { + warnx("Error creating X509 subject name object"); + goto out; + } + + sn_str = sn ? sn : gen_name(); + if (!X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC, + sn_str, -1, -1, 0)) { + warnx("Error setting X509 subject name \"%s\"", (const char *)sn_str); + goto out; + } + + if (!X509_set_subject_name(cert, subject)) { + warnx("Error setting X509 certificate subject name"); + goto out; + } + + if (!X509_set_pubkey(cert, pubkey)) { + warnx("Error setting public key for X509 certificate"); + goto out; + } + + mctx = EVP_MD_CTX_new(); + if (!mctx) { + warnx("Error allocating digest context"); + goto out; + } + + if (!EVP_DigestSignInit_ex(mctx, NULL, NULL, NULL, NULL, pkey, NULL)) { + warnx("Error initialising digest context"); + goto out; + } + + if (!X509_sign_ctx(cert, mctx)) { + warnx("Error signing certificate"); + goto out; + } + + error = 0; + +out: + EVP_MD_CTX_free(mctx); + X509_NAME_free(subject); + X509_NAME_free(issuer); + + if (error) { + X509_free(cert); + cert = NULL; + } + + return cert; +} + +/** + * A wrapper for populating gen_certs array with generated certificates. + * + * @param[in] num_keys Number of keys available. + * @param[in] keys Array of num_keys private keys for use. + * @param[in] pubkeys Array of num_keys public keys for use. + * @param[in] num_certs Number of certificates to generate. + * @param[in,out] certs A pointer to array of at least num_certs elements + * in size to populate with generated certificates. + * The certificates are supposed to be freed by the caller + * with X509_free() after use. + * @return true on success, false on failure. + */ +static bool +gen_certificates(const size_t num_keys, EVP_PKEY * const * const keys, + EVP_PKEY * const * const pubkeys, + const size_t num_certs, X509 ** const certs) +{ + OSSL_TIME last = ossl_time_now(); + + for (size_t i = 0; i < num_certs; i++) { + if (verbosity >= VERBOSITY_DEBUG_STATS) { + OSSL_TIME cur = ossl_time_now(); + + if (cur.t - last.t > OSSL_TIME_SECOND) { + fprintf(stderr, "Generating certificate %zu out of" + " %zu...\n", i + 1, num_certs); + last.t = cur.t; + } + } + certs[i] = gen_cert(keys[i % num_keys], pubkeys[i % num_keys], + NULL, NULL); + if (certs[i] == NULL) { + warnx("Generation of certificate %zu of %zu failed", + i + 1, num_certs); + return false; + } + } + + if (verbosity >= VERBOSITY_DEBUG_STATS) + fprintf(stderr, "Generated %zu certificates\n", num_certs); + + return true; +} + static X509 * load_cert_from_file(const char *path) { @@ -511,7 +874,9 @@ static void usage(char * const argv[]) { fprintf(stderr, - "Usage: %s [-t] [-v] [-q N] [-T time] [-n nonce_type:type_args] " + "Usage: %s [-t] [-v] [-q N] [-T time] [-G num] [-g num] " + "[-k num_keys] [-K keyalg[:bits][:param=value...]] " + "[-n nonce_type:type_args] " "[-C threads] certsdir [certsdir...] threadcount\n" "\t-t\tTerse output\n" "\t-v\tVerbose output. Multiple usage increases verbosity.\n" @@ -521,6 +886,15 @@ usage(char * const argv[]) "\t-T\tTimeout for the test run in seconds,\n" "\t\tcan be fractional. Default: " OPENSSL_MSTR(RUN_TIME) "\n" + "\t-G\tNumber of generated certificates. Default: " + OPENSSL_MSTR(NUM_CERTS) "\n" + "\t-g\tNumber of initially loaded generated certificates.\n" + "\t\tDefault: " OPENSSL_MSTR(NUM_LOAD_CERTS) "\n" + "\t-k\tNumber of different keys to be used\n" + "\t\tfor the generated certificates. Default: " + OPENSSL_MSTR(NUM_KEYS) "\n" + "\t-K\tAlgorithm and key size of the generated keys.\n" + "\t\tDefault: " KEY_ALGO "\n" "\t-n\tNonce configuration, supported options:\n" "\t\t\tfile:PATH - load nonce certificate from PATH;\n" "\t\t\tif PATH is relative, the provided certsdir's are searched.\n" @@ -590,15 +964,25 @@ main(int argc, char *argv[]) int i; OSSL_TIME duration; size_t ctx_share_cnt = CTX_SHARE_THREADS; + char *alg_name_storage = NULL; int ret = EXIT_FAILURE; + size_t num_gen_keys = NUM_KEYS; + const char *gen_key_algo = KEY_ALGO; + EVP_PKEY_CTX *key_ctx = NULL; + EVP_PKEY **keys = NULL; + EVP_PKEY **pubkeys = NULL; + X509 **gen_certs = NULL; int opt; int dirs_start; + size_t num_gen_certs = NUM_CERTS; + size_t num_gen_load_certs = NUM_LOAD_CERTS; size_t num_certs = 0; + size_t num_store_gen_certs = 0; struct nonce_cfg nonce_cfg; parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvq:T:n:C:")) != -1) { + while ((opt = getopt(argc, argv, "tvq:T:G:g:k:K:n:C:")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -618,6 +1002,23 @@ main(int argc, char *argv[]) case 'T': /* timeout */ timeout_us = parse_timeout(optarg); break; + case 'G': /* number of generated certs */ + num_gen_certs = parse_int(optarg, 0, INT_MAX, + "number of initially loaded generated" + " certificates"); + break; + case 'g': /* number of initially loaded generated certs */ + num_gen_load_certs = parse_int(optarg, 0, INT_MAX, + "number of initially loaded" + " generated certificates"); + break; + case 'k': /* number of generated keys */ + num_gen_keys = parse_int(optarg, 0, INT_MAX, + "number of generated keys"); + break; + case 'K': /* key type */ + gen_key_algo = optarg; + break; case 'n': /* nonce */ parse_nonce_cfg(optarg, &nonce_cfg); break; @@ -634,6 +1035,13 @@ main(int argc, char *argv[]) if (verbosity < VERBOSITY_VERBOSE) quantiles = 1; + if (num_gen_certs > 0 && num_gen_keys == 0) + errx(EXIT_FAILURE, + "Cannot generate certificates without generating keys"); + + if (num_gen_certs < num_gen_load_certs) + errx(EXIT_FAILURE, "Cannot load more certificates than generate"); + if (argv[optind] == NULL) errx(EXIT_FAILURE, "certsdir is missing"); @@ -663,15 +1071,60 @@ main(int argc, char *argv[]) " %zu", i); } + if (num_gen_keys > 0) { + key_ctx = make_pkey_ctx(gen_key_algo, &alg_name_storage); + if (key_ctx == NULL) + errx(EXIT_FAILURE, "Error creating key context"); + + keys = OPENSSL_zalloc(num_gen_keys * sizeof(*keys)); + if (keys == NULL) + errx(EXIT_FAILURE, "Error allocating generated keys array"); + + pubkeys = OPENSSL_zalloc(num_gen_keys * sizeof(*pubkeys)); + if (pubkeys == NULL) + errx(EXIT_FAILURE, "Error allocating public keys array"); + + if (!gen_keys(num_gen_keys, key_ctx, keys, pubkeys)) + errx(EXIT_FAILURE, "Error occurred during key generation"); + } + + if (num_gen_certs > 0) { + gen_certs = OPENSSL_zalloc(num_gen_certs * sizeof(*gen_certs)); + if (gen_certs == NULL) + errx(EXIT_FAILURE, "Error allocating generated certificates array"); + + if (!gen_certificates(num_gen_keys, keys, pubkeys, + num_gen_certs, gen_certs)) + errx(EXIT_FAILURE, "Error occurred during certificate generation"); + } + store = X509_STORE_new(); if (store == NULL || !X509_STORE_set_default_paths(store)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); + num_store_gen_certs = 0; + for (size_t i = 0; i < num_gen_load_certs; i++) { + if (!X509_STORE_add_cert(store, gen_certs[i])) { + warnx("Failed to add generated certificate %zu to the store\n", i); + } else { + if (verbosity >= VERBOSITY_DEBUG) + fprintf(stderr, "Successfully added generated certificate" + " %zu to the store\n", i); + num_certs++; + num_store_gen_certs++; + } + } + + if (verbosity >= VERBOSITY_DEBUG_STATS) + fprintf(stderr, "Added %zu generated certificates to the store\n", + num_store_gen_certs); + num_certs += read_certsdirs(argv + dirs_start, argc - dirs_start - 1, store); if (verbosity >= VERBOSITY_DEBUG_STATS) - fprintf(stderr, "Added %zu certificates to the store\n", num_certs); + fprintf(stderr, "Added %zu certificates to the store, %zu total\n", + num_certs - num_store_gen_certs, num_certs); report_store_size(store, "before the test run", verbosity); @@ -707,6 +1160,23 @@ main(int argc, char *argv[]) err: X509_free(x509_nonce); X509_STORE_free(store); + if (gen_certs) { + for (size_t i = 0; i < num_gen_certs; i++) + X509_free(gen_certs[i]); + } + OPENSSL_free(gen_certs); + if (pubkeys) { + for (size_t i = 0; i < num_gen_keys; i++) + EVP_PKEY_free(pubkeys[i]); + } + OPENSSL_free(pubkeys); + if (keys) { + for (size_t i = 0; i < num_gen_keys; i++) + EVP_PKEY_free(keys[i]); + } + OPENSSL_free(keys); + EVP_PKEY_CTX_free(key_ctx); + OPENSSL_free(alg_name_storage); if (thread_data != NULL) { for (size_t i = 0; i < threadcount; i++) { if (!(i % ctx_share_cnt)) From c3c4152406fcf1e382d377d7c2dd931b00403135 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Wed, 22 Oct 2025 16:38:44 +0200 Subject: [PATCH 20/23] x509storeissuer: add support for gernerated nonces Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 83679e0..e296cc2 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -53,6 +53,7 @@ enum verbosity { }; enum nonce_type { + NONCE_GENERATED, NONCE_PATH, }; @@ -457,6 +458,31 @@ gen_certificates(const size_t num_keys, EVP_PKEY * const * const keys, return true; } +static X509 * +gen_nonce(struct nonce_cfg *cfg) +{ + X509 *x509_nonce = X509_new(); + X509_NAME *x509_name_nonce = NULL; + + if (!x509_nonce) + errx(EXIT_FAILURE, "Error creating X509 nonce object"); + + x509_name_nonce = X509_NAME_new(); + if (!x509_name_nonce) + errx(EXIT_FAILURE, "Error creating X509 name nonce object"); + + if (!X509_NAME_add_entry_by_txt(x509_name_nonce, "CN", MBSTRING_ASC, + (unsigned char *) "Test NC CA", -1, -1, 0)) + errx(EXIT_FAILURE, "Error setting X509 name nonce"); + + if (!X509_set_issuer_name(x509_nonce, x509_name_nonce)) + errx(EXIT_FAILURE, "Error setting X509 nonce name"); + + X509_NAME_free(x509_name_nonce); + + return x509_nonce; +} + static X509 * load_cert_from_file(const char *path) { @@ -533,6 +559,8 @@ static X509 * make_nonce(struct nonce_cfg *cfg) { switch (cfg->type) { + case NONCE_GENERATED: + return gen_nonce(cfg); case NONCE_PATH: return load_nonce_from_path(cfg); default: @@ -896,6 +924,7 @@ usage(char * const argv[]) "\t-K\tAlgorithm and key size of the generated keys.\n" "\t\tDefault: " KEY_ALGO "\n" "\t-n\tNonce configuration, supported options:\n" + "\t\t\tgen - generated\n" "\t\t\tfile:PATH - load nonce certificate from PATH;\n" "\t\t\tif PATH is relative, the provided certsdir's are searched.\n" "\t\tDefault: " NONCE_CFG "\n" @@ -931,9 +960,12 @@ parse_timeout(const char * const optarg) static void parse_nonce_cfg(const char * const optarg, struct nonce_cfg *cfg) { + static const char gen[] = "gen"; static const char file_pfx[] = "file:"; - if (strncmp(optarg, file_pfx, sizeof(file_pfx) - 1) == 0) { + if (strncmp(optarg, gen, sizeof(gen)) == 0) { + cfg->type = NONCE_GENERATED; + } else if (strncmp(optarg, file_pfx, sizeof(file_pfx) - 1) == 0) { cfg->type = NONCE_PATH; cfg->path = optarg + sizeof(file_pfx) - 1; } else { From 806e3ee79579b31957f424ca2162d3ddf90038bb Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Wed, 22 Oct 2025 16:47:44 +0200 Subject: [PATCH 21/23] x509storeissuer: add ability to add certificates to the store during the run Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 118 +++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 10 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index e296cc2..f53c78a 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -34,6 +34,8 @@ #define NUM_LOAD_CERTS 128 #define NUM_KEYS 16 #define KEY_ALGO "rsa:2048" +#define W_PROBABILITY 50 +#define MAX_WRITERS 0 #define RUN_TIME 5 #define QUANTILES 5 #define NONCE_CFG "file:servercert.pem" @@ -41,6 +43,8 @@ static size_t timeout_us = RUN_TIME * 1000000; static size_t quantiles = QUANTILES; +static size_t max_writers = MAX_WRITERS; +static size_t w_probability = W_PROBABILITY * 65536 / 100; enum verbosity { VERBOSITY_TERSE, @@ -52,6 +56,11 @@ enum verbosity { VERBOSITY_MAX__ }; +static enum mode { + MODE_R, + MODE_RW, /* "MODE_W" is just MODE_RW with 100% write probability */ +} mode = MODE_R; + enum nonce_type { NONCE_GENERATED, NONCE_PATH, @@ -61,6 +70,7 @@ struct call_times { uint64_t duration; uint64_t total_count; uint64_t total_found; + uint64_t total_added; uint64_t min_count; uint64_t max_count; double avg; @@ -84,8 +94,11 @@ struct thread_data { struct { uint64_t count; uint64_t found; + uint64_t added_certs; OSSL_TIME end_time; } *q_data; + size_t cert_count; + X509 **certs; X509_STORE_CTX *ctx; } *thread_data; @@ -718,6 +731,7 @@ do_x509storeissuer(size_t num) OSSL_TIME q_end; size_t q = 0; size_t count = 0; + size_t add = 0; size_t found = 0; td->start_time = ossl_time_now(); @@ -725,17 +739,30 @@ do_x509storeissuer(size_t num) q_end.t = duration.t / quantiles + td->start_time.t; do { - if (X509_STORE_CTX_get1_issuer(&issuer, td->ctx, x509_nonce) != 0) { - found++; - X509_free(issuer); + if (td->cert_count > 0 && (rand() % 65536 < w_probability)) { + size_t cert_id = add % td->cert_count; + + if (!X509_STORE_add_cert(store, td->certs[cert_id])) { + warnx("thread %zu: Failed to add generated certificate %zu" + " to the store", num, cert_id); + } else { + add++; + } + } else { + if (X509_STORE_CTX_get1_issuer(&issuer, td->ctx, x509_nonce) != 0) { + found++; + X509_free(issuer); + } + issuer = NULL; } - issuer = NULL; + count++; if ((count & 0x3f) == 0) { time = ossl_time_now(); if (time.t >= q_end.t) { td->q_data[q].count = count; td->q_data[q].found = found; + td->q_data[q].added_certs = add; td->q_data[q].end_time = time; q_end.t = (duration.t * (++q + 1)) / quantiles + td->start_time.t; } @@ -744,6 +771,7 @@ do_x509storeissuer(size_t num) td->q_data[quantiles - 1].count = count; td->q_data[quantiles - 1].found = found; + td->q_data[quantiles - 1].added_certs = add; td->q_data[quantiles - 1].end_time = time; } @@ -790,10 +818,13 @@ get_calltimes(struct call_times *times, int verbosity) (q ? thread_data[i].q_data[q - 1].count : 0); uint64_t found = thread_data[i].q_data[q].found - (q ? thread_data[i].q_data[q - 1].found : 0); + uint64_t add = thread_data[i].q_data[q].added_certs - + (q ? thread_data[i].q_data[q - 1].added_certs : 0); times[q].duration += thread_data[i].q_data[q].end_time.t - start_t; times[q].total_count += count; times[q].total_found += found; + times[q].total_added += add; } } @@ -801,6 +832,7 @@ get_calltimes(struct call_times *times, int verbosity) times[quantiles].duration += times[q].duration; times[quantiles].total_count += times[q].total_count; times[quantiles].total_found += times[q].total_found; + times[quantiles].total_added += times[q].total_added; } for (size_t q = (quantiles == 1); q <= quantiles; q++) @@ -885,14 +917,18 @@ report_result(int verbosity) printf(": avg: %9.3lf us, median: %9.3lf us" ", min: %9.3lf us @thread %3zu, max: %9.3lf us @thread %3zu" ", stddev: %9.3lf us (%8.4lf%%)" - ", hits %9zu of %9zu (%8.4lf%%)\n", + ", hits %9zu of %9zu (%8.4lf%%)" + ", added certs: %zu\n", times[i].avg, times[i].median, times[i].min, times[i].min_idx, times[i].max, times[i].max_idx, times[i].stddev, 100.0 * times[i].stddev / times[i].avg, - times[i].total_found, times[i].total_count, - 100.0 * times[i].total_found / (times[i].total_count)); + times[i].total_found, + (times[i].total_count - times[i].total_added), + 100.0 * times[i].total_found + / (times[i].total_count - times[i].total_added), + times[i].total_added); } break; } @@ -904,8 +940,8 @@ usage(char * const argv[]) fprintf(stderr, "Usage: %s [-t] [-v] [-q N] [-T time] [-G num] [-g num] " "[-k num_keys] [-K keyalg[:bits][:param=value...]] " - "[-n nonce_type:type_args] " - "[-C threads] certsdir [certsdir...] threadcount\n" + "[-n nonce_type:type_args] [-m mode] [-w writer_threads] " + "[-W percentage] [-C threads] certsdir [certsdir...] threadcount\n" "\t-t\tTerse output\n" "\t-v\tVerbose output. Multiple usage increases verbosity.\n" "\t-q\tGather information about temporal N-quantiles.\n" @@ -928,6 +964,13 @@ usage(char * const argv[]) "\t\t\tfile:PATH - load nonce certificate from PATH;\n" "\t\t\tif PATH is relative, the provided certsdir's are searched.\n" "\t\tDefault: " NONCE_CFG "\n" + "\t-m\tTest mode, can be one of r, rw. Default: r\n" + "\t-w\tMaximum number of threads that attempt addition\n" + "\t\tof the new certificates to the store in rw mode,\n" + "\t\t0 is unlimited. Default: " OPENSSL_MSTR(MAX_WRITERS) "\n" + "\t-W\tProbability of a certificate being written\n" + "\t\tto the store, instead of being queried,\n" + "\t\tin percents. Default: " OPENSSL_MSTR(W_PROBABILITY) "\n" "\t-C\tNumber of threads that share the same X.509\n" "\t\tstore context object. Default: " OPENSSL_MSTR(CTX_SHARE_THREADS) "\n" @@ -951,8 +994,23 @@ parse_timeout(const char * const optarg) return (size_t)(timeout_s * 1e6); } +static double +parse_probability(const char * const optarg) +{ + char *endptr = NULL; + double prob; + + prob = strtod(optarg, &endptr); + + if (endptr == NULL || *endptr != '\0' || prob < 0 || prob > 100) + errx(EXIT_FAILURE, "incorrect probability value: \"%s\"", optarg); + + return prob; +} + /** * Parse nonce configuration string. Currently supported formats: + * * "gen" - generate a nonce certificate * * "file:PATH" - where PATH is either a relative path (that will be then * checked against the list of directories provided), * or an absolute one. @@ -1014,7 +1072,7 @@ main(int argc, char *argv[]) parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvq:T:G:g:k:K:n:C:")) != -1) { + while ((opt = getopt(argc, argv, "tvq:T:G:g:k:K:m:n:w:W:C:")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -1051,9 +1109,24 @@ main(int argc, char *argv[]) case 'K': /* key type */ gen_key_algo = optarg; break; + case 'm': /* mode */ + if (strcasecmp(optarg, "r") == 0) { + mode = MODE_R; + } else if (strcasecmp(optarg, "rw") == 0) { + mode = MODE_RW; + } else { + errx(EXIT_FAILURE, "Unknown mode: \"%s\"", optarg); + } + break; case 'n': /* nonce */ parse_nonce_cfg(optarg, &nonce_cfg); break; + case 'w': /* maximum writers */ + max_writers = parse_int(optarg, 0, INT_MAX, + "maximum number of writers"); + case 'W': /* percent of writes */ + w_probability = (size_t) (parse_probability(optarg) * 65536 / 100); + break; case 'C': /* how many threads share X509_STORE_CTX */ ctx_share_cnt = parse_int(optarg, 1, INT_MAX, "X509_STORE_CTX share degree"); @@ -1074,6 +1147,11 @@ main(int argc, char *argv[]) if (num_gen_certs < num_gen_load_certs) errx(EXIT_FAILURE, "Cannot load more certificates than generate"); + if (num_gen_certs == num_gen_load_certs && mode == MODE_RW) + errx(EXIT_FAILURE, "No generated certificates to use after" + " the initially loaded ones, please increase" + " -G to be more than -g"); + if (argv[optind] == NULL) errx(EXIT_FAILURE, "certsdir is missing"); @@ -1177,6 +1255,23 @@ main(int argc, char *argv[]) } } + if (mode == MODE_RW) { + size_t writers = max_writers ? OSSL_MIN(max_writers, threadcount) + : threadcount; + size_t cnt = num_gen_certs - num_gen_load_certs; + + /* + * Point each writer thread at the part of the generated certs + * array it uses for store population. + */ + for (size_t i = 0; i < writers; i++) { + size_t offs = (cnt * i) / writers; + + thread_data[i].certs = gen_certs + num_gen_load_certs + offs; + thread_data[i].cert_count = OSSL_MAX(cnt / writers, 1); + } + } + max_time = ossl_time_add(ossl_time_now(), ossl_us2time(timeout_us)); if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) @@ -1185,6 +1280,9 @@ main(int argc, char *argv[]) if (error) errx(EXIT_FAILURE, "Error during test"); + if (mode == MODE_RW) + report_store_size(store, "after the test run", verbosity); + report_result(verbosity); ret = EXIT_SUCCESS; From 9147f7ff669b269f986ce50ec88835b5ad3c4b37 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Thu, 23 Oct 2025 13:01:43 +0200 Subject: [PATCH 22/23] x509storeissuer: make thread_data cache-line-aligned Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index f53c78a..3acce44 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -89,7 +89,16 @@ struct nonce_cfg { size_t num_dirs; }; -struct thread_data { +/* Cache line size is either 32 or 64 bytes on most arches of interest */ +#if defined(__GNUC__) || defined(__clang__) +# define ALIGN64 __attribute((aligned(64))) +#elif defined(_MSC_VER) +# define ALIGN64 __declspec(align(64)) +#else +# define ALIGN64 +#endif + +ALIGN64 struct thread_data { OSSL_TIME start_time; struct { uint64_t count; From 4b335f7242ade07861065b99c592a723dfb5acc4 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Thu, 23 Oct 2025 17:03:51 +0200 Subject: [PATCH 23/23] .github/workflows/test.yml: do not build OpenSSL apps and tests Signed-off-by: Eugene Syromiatnikov --- .github/workflows/test.yml | 160 ++++++++++++++++++++++++++----------- 1 file changed, 115 insertions(+), 45 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5b8ca3c..bc6d514 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,13 +9,27 @@ jobs: strategy: fail-fast: false matrix: - openssl-branch: - - "openssl-3.0" - - "openssl-3.2" - - "openssl-3.3" - - "openssl-3.4" - - "openssl-3.5" - - "master" + release: [ + { + openssl-branch: "openssl-3.0", + configopts: 'no-tests', + }, { + openssl-branch: "openssl-3.2", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.3", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.4", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.5", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "master", + configopts: 'no-apps no-tests', + } + ] runs-on: "ubuntu-latest" container: "docker.io/library/debian:11" steps: @@ -27,13 +41,13 @@ jobs: uses: "actions/checkout@v5" with: repository: "openssl/openssl" - ref: ${{ matrix.openssl-branch }} + ref: ${{ matrix.release.openssl-branch }} fetch-depth: 1 path: "openssl" - name: "Config openssl build" working-directory: "./openssl" run: | - ./config --prefix="$PWD/dist" + ./config ${{ matrix.release.configopts }} --prefix="$PWD/dist" - name: "Build openssl" working-directory: "./openssl" run: | @@ -59,13 +73,27 @@ jobs: strategy: fail-fast: false matrix: - openssl-branch: - - "openssl-3.0" - - "openssl-3.2" - - "openssl-3.3" - - "openssl-3.4" - - "openssl-3.5" - - "master" + release: [ + { + openssl-branch: "openssl-3.0", + configopts: 'no-tests', + }, { + openssl-branch: "openssl-3.2", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.3", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.4", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.5", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "master", + configopts: 'no-apps no-tests', + } + ] runs-on: "ubuntu-latest" container: "docker.io/library/ubuntu:20.04" steps: @@ -79,13 +107,13 @@ jobs: uses: "actions/checkout@v5" with: repository: "openssl/openssl" - ref: ${{ matrix.openssl-branch }} + ref: ${{ matrix.release.openssl-branch }} fetch-depth: 1 path: "openssl" - name: "Config openssl build" working-directory: "./openssl" run: | - ./config --prefix="$PWD/dist" + ./config ${{ matrix.release.configopts }} --prefix="$PWD/dist" - name: "Build openssl" working-directory: "./openssl" run: | @@ -111,13 +139,27 @@ jobs: strategy: fail-fast: false matrix: - openssl-branch: - - "openssl-3.0" - - "openssl-3.2" - - "openssl-3.3" - - "openssl-3.4" - - "openssl-3.5" - - "master" + release: [ + { + openssl-branch: "openssl-3.0", + configopts: 'no-tests', + }, { + openssl-branch: "openssl-3.2", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.3", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.4", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.5", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "master", + configopts: 'no-apps no-tests', + } + ] runs-on: "ubuntu-latest" steps: - name: "Install prerequisites" @@ -132,7 +174,7 @@ jobs: uses: "actions/checkout@v5" with: repository: "openssl/openssl" - ref: ${{ matrix.openssl-branch }} + ref: ${{ matrix.release.openssl-branch }} fetch-depth: 1 path: "openssl" - name: "Config openssl build" @@ -143,7 +185,7 @@ jobs: shutdown_vm: false run: | cd openssl - ./config --prefix="$PWD/dist" + ./config ${{ matrix.release.configopts }} --prefix="$PWD/dist" - name: "Build openssl" uses: "cross-platform-actions/action@fe0167d8082ac584754ef3ffb567fded22642c7d" #v0.27.0 with: @@ -189,13 +231,27 @@ jobs: strategy: fail-fast: false matrix: - openssl-branch: - - "openssl-3.0" - - "openssl-3.2" - - "openssl-3.3" - - "openssl-3.4" - - "openssl-3.5" - - "master" + release: [ + { + openssl-branch: "openssl-3.0", + configopts: 'no-tests', + }, { + openssl-branch: "openssl-3.2", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.3", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.4", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.5", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "master", + configopts: 'no-apps no-tests', + } + ] runs-on: "windows-latest" steps: - name: "Install prerequisites" @@ -206,7 +262,7 @@ jobs: uses: "actions/checkout@v5" with: repository: "openssl/openssl" - ref: ${{ matrix.openssl-branch }} + ref: ${{ matrix.release.openssl-branch }} fetch-depth: 1 path: "openssl" - name: "Config openssl build" @@ -214,7 +270,7 @@ jobs: shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - perl Configure no-makedepend + perl Configure no-makedepend ${{ matrix.release.configopts }} - name: "Build openssl" working-directory: ".\\openssl" shell: cmd @@ -238,26 +294,40 @@ jobs: strategy: fail-fast: false matrix: - openssl-branch: - - "openssl-3.0" - - "openssl-3.2" - - "openssl-3.3" - - "openssl-3.4" - - "openssl-3.5" - - "master" + release: [ + { + openssl-branch: "openssl-3.0", + configopts: 'no-tests', + }, { + openssl-branch: "openssl-3.2", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.3", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.4", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "openssl-3.5", + configopts: 'no-apps no-tests', + }, { + openssl-branch: "master", + configopts: 'no-apps no-tests', + } + ] runs-on: "macos-latest" steps: - name: "Checkout openssl" uses: "actions/checkout@v5" with: repository: "openssl/openssl" - ref: ${{ matrix.openssl-branch }} + ref: ${{ matrix.release.openssl-branch }} fetch-depth: 1 path: "openssl" - name: "Config openssl build" working-directory: "./openssl" run: | - ./config --prefix="$PWD/dist" + ./config ${{ matrix.release.configopts }} --prefix="$PWD/dist" - name: "Build openssl" working-directory: "./openssl" run: |