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: | 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/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/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 0bb7a53..5e848ee 100644 --- a/source/perflib/err.h +++ b/source/perflib/err.h @@ -11,13 +11,26 @@ # define OSSL_PERFLIB_ERR_H # pragma once -#include +# if !defined(_WIN32) + +# include + +# else /* _WIN32 */ + +# include 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 */ #endif 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/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 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 941d432..3acce44 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -7,10 +7,15 @@ * https://www.openssl.org/source/license.html */ -#include +#include +#include +#include #include +#include #include +#include #ifndef _WIN32 +# include # include # include #else @@ -19,155 +24,1305 @@ # 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 W_PROBABILITY 50 +#define MAX_WRITERS 0 #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; +static size_t max_writers = MAX_WRITERS; +static size_t w_probability = W_PROBABILITY * 65536 / 100; + +enum verbosity { + VERBOSITY_TERSE, + VERBOSITY_DEFAULT, + VERBOSITY_VERBOSE, + VERBOSITY_DEBUG_STATS, + VERBOSITY_DEBUG, + + 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, +}; + +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; + 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; + char **dirs; + size_t num_dirs; +}; + +/* 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; + 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; -static int err = 0; +static int error = 0; +static int verbosity = VERBOSITY_DEFAULT; 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 void do_x509storeissuer(size_t num) +#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) { - X509_STORE_CTX *ctx = X509_STORE_CTX_new(); - X509 *issuer = NULL; - OSSL_TIME time; + EVP_PKEY_CTX *ctx = NULL; + const char *alg; + const char *p = strchr(algstr, ':'); - if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509, NULL)) { - printf("Failed to initialise X509_STORE_CTX\n"); - err = 1; + alg = p ? *alg_storage = OPENSSL_strndup(algstr, p - algstr) : algstr; + if (alg == NULL) { + warnx("Error getting algorithm name from \"%s\"", algstr); goto err; } - counts[num] = 0; + ctx = EVP_PKEY_CTX_new_from_name(NULL, alg, NULL); + if (ctx == NULL) { + warnx("Error allocating keygen context"); + goto err; + } - 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) != 0) { - printf("Unexpected result from X509_STORE_CTX_get1_issuer\n"); - err = 1; - X509_free(issuer); + 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; } - issuer = NULL; - counts[num]++; - time = ossl_time_now(); - } while (time.t < max_time.t); + p = endptr; - err: - X509_STORE_CTX_free(ctx); + 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; } -int main(int argc, char *argv[]) +static EVP_PKEY * +gen_key(EVP_PKEY_CTX *ctx) { - int i; - OSSL_TIME duration; - size_t total_count = 0; - double avcalltime; - int terse = 0; - char *cert = NULL; - int ret = EXIT_FAILURE; - BIO *bio = NULL; - int opt; + EVP_PKEY *res = NULL; - while ((opt = getopt(argc, argv, "t")) != -1) { - switch (opt) { - case 't': - terse = 1; - break; - default: - printf("Usage: %s [-t] certsdir threadcount\n", basename(argv[0])); - printf("-t - terse output\n"); - return EXIT_FAILURE; - } + if (EVP_PKEY_keygen(ctx, &res) <= 0) { + warnx("Error generating key"); + + return NULL; } - if (argv[optind] == NULL) { - printf("certsdir is missing\n"); + 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; } - cert = perflib_mk_file_path(argv[optind], "servercert.pem"); - if (cert == NULL) { - printf("Failed to allocate cert\n"); + + if (!PEM_write_bio_PUBKEY(bio, pkey)) { + warnx("Error writing public key to BIO"); goto err; } - optind++; - if (argv[optind] == NULL) { - printf("threadcount is missing\n"); + if (BIO_seek(bio, 0) < 0) { + warnx("Error resetting BIO cursor"); goto err; } - threadcount = atoi(argv[optind]); - if (threadcount < 1) { - printf("threadcount must be > 0\n"); + + pubkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (pubkey == NULL) { + warnx("Error reading pubkey from BIO"); goto err; } - store = X509_STORE_new(); - if (store == NULL || !X509_STORE_set_default_paths(store)) { - printf("Failed to create X509_STORE\n"); - 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; + } } - bio = BIO_new_file(cert, "rb"); + 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 * +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) +{ + BIO *bio = BIO_new_file(path, "rb"); + X509 *x509 = NULL; + if (bio == NULL) { - printf("Unable to load certificate\n"); - goto err; + warnx("Unable to create BIO for reading \"%s\"", path); + return NULL; } + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); if (x509 == NULL) { - printf("Failed to read certificate\n"); - goto err; + if (verbosity >= VERBOSITY_DEBUG) + warnx("Failed to read certificate \"%s\"", path); } + BIO_free(bio); - bio = NULL; - counts = OPENSSL_malloc(sizeof(size_t) * threadcount); - if (counts == NULL) { - printf("Failed to create counts array\n"); - goto err; + 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_GENERATED: + return gen_nonce(cfg); + case NONCE_PATH: + return load_nonce_from_path(cfg); + default: + errx(EXIT_FAILURE, "Unknown nonce type: %lld", (long long) cfg->type); } +} + +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; - max_time = ossl_time_add(ossl_time_now(), ossl_seconds2time(RUN_TIME)); + 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; + } - if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) { - printf("Failed to run the test\n"); - goto err; + x509 = load_cert_from_file(path); + if (x509 == NULL) { + goto out; } - if (err) { - printf("Error during test\n"); - goto err; + 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) +{ + struct thread_data *td = thread_data + num; + 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 add = 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 (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; + } + + 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; + } + } + } while (time.t < max_time.t); + + 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; +} + +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 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); + 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; + } } - for (i = 0; i < threadcount; i++) - total_count += counts[i]; + 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; + times[quantiles].total_added += times[q].total_added; + } + + 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; - avcalltime = (double)RUN_TIME * 1e6 * threadcount / total_count; + 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); + } - if (terse) - printf("%lf\n", avcalltime); - else - printf("Average time per X509_STORE_CTX_get1_issuer() call: %lfus\n", - avcalltime); + 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%%)" + ", 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 - times[i].total_added), + 100.0 * times[i].total_found + / (times[i].total_count - times[i].total_added), + times[i].total_added); + } + break; + } +} + +static void +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] [-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" + "\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" + "\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\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" + "\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" + , 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); +} + +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. + */ +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, 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 { + errx(EXIT_FAILURE, "incorrect nonce configuration: \"%s\"", optarg); + } +} + +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; + 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:G:g:k:K:m:n:w:W:C:")) != -1) { + switch (opt) { + 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 'q': /* quantiles */ + quantiles = parse_int(optarg, 1, INT_MAX, + "number of quantiles"); + break; + 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 '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"); + break; + default: + usage(argv); + return EXIT_FAILURE; + } + } + + 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 (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"); + + dirs_start = 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 = argc - 1 - dirs_start; + + if (optind >= argc) + errx(EXIT_FAILURE, "threadcount is missing"); + + 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"); + + 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); + } + + 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, %zu total\n", + num_certs - num_store_gen_certs, num_certs); + + 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"); + + 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); + } + } + + 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)) + errx(EXIT_FAILURE, "Failed to run the test"); + + 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; err: + X509_free(x509_nonce); X509_STORE_free(store); - X509_free(x509); - BIO_free(bio); - OPENSSL_free(cert); - OPENSSL_free(counts); + 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)) + X509_STORE_CTX_free(thread_data[i].ctx); + OPENSSL_free(thread_data[i].q_data); + } + } + OPENSSL_free(thread_data); return ret; }