From db30ae8610807e217b8bdd74c7724c5ce024cbac Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 24 Mar 2026 20:23:21 +0100 Subject: [PATCH 01/17] windows setup --- .gitignore | 5 +++++ wolfclu.sln | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c8f7499c..706eb8d5 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,8 @@ src/stamp-h1 .project .settings/ AGENTS.md +Win32/ +x64/ +.vs/ + +CLAUDE.md diff --git a/wolfclu.sln b/wolfclu.sln index 9aa683f3..a3c44ee0 100644 --- a/wolfclu.sln +++ b/wolfclu.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.32014.148 @@ -17,8 +17,8 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {CFC6FB69-7DA4-4E35-851E-776010E92FB3}.Debug|Win32.ActiveCfg = Debug|Win32 {CFC6FB69-7DA4-4E35-851E-776010E92FB3}.Debug|Win32.Build.0 = Debug|Win32 - {CFC6FB69-7DA4-4E35-851E-776010E92FB3}.Debug|x64.ActiveCfg = Debug|Win32 - {CFC6FB69-7DA4-4E35-851E-776010E92FB3}.Debug|x64.Build.0 = Debug|Win32 + {CFC6FB69-7DA4-4E35-851E-776010E92FB3}.Debug|x64.ActiveCfg = Debug|x64 + {CFC6FB69-7DA4-4E35-851E-776010E92FB3}.Debug|x64.Build.0 = Debug|x64 {CFC6FB69-7DA4-4E35-851E-776010E92FB3}.Debug|x86.ActiveCfg = Debug|Win32 {CFC6FB69-7DA4-4E35-851E-776010E92FB3}.Debug|x86.Build.0 = Debug|Win32 {CFC6FB69-7DA4-4E35-851E-776010E92FB3}.Release|Win32.ActiveCfg = Release|Win32 From cb5d98ad3c7f208b266fd6bdf45c73ed273758a1 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 25 Mar 2026 17:59:52 +0100 Subject: [PATCH 02/17] Add SM3 hash type in wolfCLU_CertSign function --- src/x509/clu_x509_sign.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/x509/clu_x509_sign.c b/src/x509/clu_x509_sign.c index 7d5625cd..038892a8 100644 --- a/src/x509/clu_x509_sign.c +++ b/src/x509/clu_x509_sign.c @@ -1287,6 +1287,7 @@ int wolfCLU_CertSign(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* x509) #ifdef WOLFSSL_SHAKE256 case WC_HASH_TYPE_SHAKE256: #endif + case WC_HASH_TYPE_SM3: #endif default: wolfCLU_LogError("Unsupported hash type"); From d730ccf1b5db132605b1565a693b1bca6cdd7d77 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 25 Mar 2026 18:29:20 +0100 Subject: [PATCH 03/17] Rewrite tests into python scripts for easier portability - Add shared test helper and cross-platform test runner - Extract binary lookup and run_wolfssl() into tests/wolfclu_test.py so all test files share the same logic for finding the wolfssl binary across Linux (./wolfssl) and Windows (x64/Debug/wolfssl.exe etc.). - Add tests/run_tests.py which discovers and runs all *-test.py files, intended for Windows where `make check` is not available. - Enable PKCS7 and CRL in MSVC - Fix three Windows bugs uncovered by the test: - Add StartTCP() (WSAStartup) in client and server setup so Winsock is initialized before gethostbyname/connect calls - Pass SNI hostname (-S flag) to the underlying client_test so modern TLS servers accept the connection - Implement checkStdin() for Windows using WaitForSingleObject so s_client exits promptly when stdin is a closed pipe - Update user_settings.h with defines required for TLS 1.3 and full wolfCLU functionality: WOLFSSL_TLS13, HAVE_HKDF, WC_RSA_PSS, HAVE_SUPPORTED_CURVES, HAVE_FFDHE_2048, HAVE_SNI. - Fix run_wolfssl() to use stdin=DEVNULL when no input is provided, preventing subprocesses from blocking on inherited stdin (e.g. over SSH/network sessions on Windows). - Also add WOLFCLU_SKIP_SLOW_TESTS env var to skip slow tests on Windows, and optimize large file creation to a single write. - Add HAVE_PKCS12 to Windows user_settings.h. Skip binary DER stdin test on Windows where pipe binary mode is unreliable. - Combine ocsp-test.sh and ocsp-interop-test.sh into a single Python test module that tests all client/responder combinations (wolfssl and openssl). - Add StartTCP() in OCSP setup for Winsock initialization on Windows. - Add HAVE_OCSP and HAVE_OCSP_RESPONDER to Windows user_settings.h. - Rewrite base64 test from bash to Python unittest - Rewrite bench test from bash to Python unittest - Rewrite client test to Python and fix Windows networking bugs - Replace tests/client/client-test.sh with a cross-platform Python unittest. - Rewrite dgst test from bash to Python unittest - Rewrite dh test from bash to Python unittest - Rewrite dsa test from bash to Python unittest - Rewrite enc test from bash to Python unittest - Rewrite genkey sign/verify test from bash to Python unittest - Rewrite hash test from bash to Python unittest - Rewrite pkcs7/pkcs8/pkcs12 tests from bash to Python unittest - Rewrite pkey/rsa/ecparam tests from bash to Python unittest - Rewrite rand test from bash to Python unittest - Rewrite server test from bash to Python unittest - Rewrite encdec test from bash to Python unittest - Rewrite x509/CRL tests from bash to Python unittest - Rewrite OCSP tests from bash to Python unittest - Rewrite OCSP SCGI test from bash to Python, drop nginx dependency - Replace nginx + bash with a pure-Python HTTP-to-SCGI proxy using stdlib http.server and raw sockets for the SCGI netstring protocol. No external dependencies needed. - Remove nginx from CI apt-get installs since it is no longer required for testing. --- .github/workflows/fsanitize-check.yml | 4 +- .github/workflows/ubuntu-check.yml | 4 +- .gitignore | 2 +- Makefile.am | 4 + configure.ac | 2 + ide/winvs/user_settings.h | 11 + src/client/client.c | 19 +- src/client/clu_client_setup.c | 12 +- src/ocsp/clu_ocsp.c | 2 + src/server/clu_server_setup.c | 1 + src/tools/clu_funcs.c | 9 +- tests/base64/base64-test.py | 108 +++ tests/base64/base64-test.sh | 70 -- tests/base64/include.am | 2 +- tests/bench/bench-test.py | 28 + tests/bench/bench-test.sh | 24 - tests/bench/include.am | 2 +- tests/client/client-test.py | 55 ++ tests/client/client-test.sh | 29 - tests/client/include.am | 2 +- tests/dgst/dgst-test.py | 216 +++++ tests/dgst/dgst-test.sh | 98 --- tests/dgst/include.am | 2 +- tests/dh/dh-test.py | 111 +++ tests/dh/dh-test.sh | 111 --- tests/dh/include.am | 2 +- tests/dsa/dsa-test.py | 98 +++ tests/dsa/dsa-test.sh | 102 --- tests/dsa/include.am | 2 +- tests/encrypt/enc-test.py | 279 +++++++ tests/encrypt/enc-test.sh | 189 ----- tests/encrypt/include.am | 2 +- tests/genkey_sign_ver/genkey-sign-ver-test.py | 253 ++++++ tests/genkey_sign_ver/genkey-sign-ver-test.sh | 320 -------- tests/genkey_sign_ver/include.am | 2 +- tests/hash/hash-test.py | 86 ++ tests/hash/hash-test.sh | 98 --- tests/hash/include.am | 2 +- tests/ocsp-scgi/include.am | 6 +- tests/ocsp-scgi/ocsp-scgi-test.py | 291 +++++++ tests/ocsp-scgi/ocsp-scgi-test.sh | 485 ----------- tests/ocsp-scgi/scgi_params | 16 - tests/ocsp/include.am | 2 +- tests/ocsp/ocsp-interop-test.sh | 520 ------------ tests/ocsp/ocsp-test.py | 338 ++++++++ tests/ocsp/ocsp-test.sh | 82 -- tests/pkcs/include.am | 6 +- tests/pkcs/pkcs12-test.py | 75 ++ tests/pkcs/pkcs12-test.sh | 67 -- tests/pkcs/pkcs7-test.py | 72 ++ tests/pkcs/pkcs7-test.sh | 61 -- tests/pkcs/pkcs8-test.py | 116 +++ tests/pkcs/pkcs8-test.sh | 85 -- tests/pkey/ecparam-test.py | 165 ++++ tests/pkey/ecparam-test.sh | 109 --- tests/pkey/include.am | 6 +- tests/pkey/pkey-test.py | 88 ++ tests/pkey/pkey-test.sh | 80 -- tests/pkey/rsa-test.py | 143 ++++ tests/pkey/rsa-test.sh | 142 ---- tests/rand/include.am | 2 +- tests/rand/rand-test.py | 47 ++ tests/rand/rand-test.sh | 43 - tests/run_tests.py | 45 ++ tests/server/include.am | 2 +- tests/server/server-test.py | 72 ++ tests/server/server-test.sh | 46 -- tests/testEncDec/README.md | 13 - tests/testEncDec/encdec-test.py | 123 +++ tests/testEncDec/include.am | 6 + tests/testEncDec/test_aescbc_3des_cam.sh | 30 - tests/testEncDec/test_aesctr.sh | 9 - tests/wolfclu_test.py | 48 ++ tests/x509/CRL-verify-test.py | 164 ++++ tests/x509/CRL-verify-test.sh | 92 --- tests/x509/include.am | 10 +- tests/x509/x509-ca-test.py | 751 ++++++++++++++++++ tests/x509/x509-ca-test.sh | 354 --------- tests/x509/x509-process-test.py | 508 ++++++++++++ tests/x509/x509-process-test.sh | 367 --------- tests/x509/x509-req-test.py | 729 +++++++++++++++++ tests/x509/x509-req-test.sh | 338 -------- tests/x509/x509-verify-test.py | 155 ++++ tests/x509/x509-verify-test.sh | 121 --- 84 files changed, 5250 insertions(+), 4143 deletions(-) create mode 100644 tests/base64/base64-test.py delete mode 100755 tests/base64/base64-test.sh create mode 100644 tests/bench/bench-test.py delete mode 100755 tests/bench/bench-test.sh create mode 100644 tests/client/client-test.py delete mode 100755 tests/client/client-test.sh create mode 100644 tests/dgst/dgst-test.py delete mode 100755 tests/dgst/dgst-test.sh create mode 100644 tests/dh/dh-test.py delete mode 100755 tests/dh/dh-test.sh create mode 100644 tests/dsa/dsa-test.py delete mode 100755 tests/dsa/dsa-test.sh create mode 100644 tests/encrypt/enc-test.py delete mode 100755 tests/encrypt/enc-test.sh create mode 100644 tests/genkey_sign_ver/genkey-sign-ver-test.py delete mode 100755 tests/genkey_sign_ver/genkey-sign-ver-test.sh create mode 100644 tests/hash/hash-test.py delete mode 100755 tests/hash/hash-test.sh create mode 100644 tests/ocsp-scgi/ocsp-scgi-test.py delete mode 100755 tests/ocsp-scgi/ocsp-scgi-test.sh delete mode 100644 tests/ocsp-scgi/scgi_params delete mode 100755 tests/ocsp/ocsp-interop-test.sh create mode 100644 tests/ocsp/ocsp-test.py delete mode 100755 tests/ocsp/ocsp-test.sh create mode 100644 tests/pkcs/pkcs12-test.py delete mode 100755 tests/pkcs/pkcs12-test.sh create mode 100644 tests/pkcs/pkcs7-test.py delete mode 100755 tests/pkcs/pkcs7-test.sh create mode 100644 tests/pkcs/pkcs8-test.py delete mode 100755 tests/pkcs/pkcs8-test.sh create mode 100644 tests/pkey/ecparam-test.py delete mode 100755 tests/pkey/ecparam-test.sh create mode 100644 tests/pkey/pkey-test.py delete mode 100755 tests/pkey/pkey-test.sh create mode 100644 tests/pkey/rsa-test.py delete mode 100755 tests/pkey/rsa-test.sh create mode 100644 tests/rand/rand-test.py delete mode 100755 tests/rand/rand-test.sh create mode 100755 tests/run_tests.py create mode 100644 tests/server/server-test.py delete mode 100755 tests/server/server-test.sh delete mode 100644 tests/testEncDec/README.md create mode 100644 tests/testEncDec/encdec-test.py create mode 100644 tests/testEncDec/include.am delete mode 100755 tests/testEncDec/test_aescbc_3des_cam.sh delete mode 100755 tests/testEncDec/test_aesctr.sh create mode 100644 tests/wolfclu_test.py create mode 100644 tests/x509/CRL-verify-test.py delete mode 100755 tests/x509/CRL-verify-test.sh create mode 100644 tests/x509/x509-ca-test.py delete mode 100755 tests/x509/x509-ca-test.sh create mode 100644 tests/x509/x509-process-test.py delete mode 100755 tests/x509/x509-process-test.sh create mode 100644 tests/x509/x509-req-test.py delete mode 100755 tests/x509/x509-req-test.sh create mode 100644 tests/x509/x509-verify-test.py delete mode 100755 tests/x509/x509-verify-test.sh diff --git a/.github/workflows/fsanitize-check.yml b/.github/workflows/fsanitize-check.yml index fb1cd4cb..e2e93eab 100644 --- a/.github/workflows/fsanitize-check.yml +++ b/.github/workflows/fsanitize-check.yml @@ -67,8 +67,8 @@ jobs: # Don't prompt for anything export DEBIAN_FRONTEND=noninteractive sudo apt-get update - # openssl and nginx used for ocsp testing - sudo apt-get install -y openssl nginx + # openssl used for ocsp interop testing + sudo apt-get install -y openssl - name: Checking cache for wolfssl uses: actions/cache@v4 diff --git a/.github/workflows/ubuntu-check.yml b/.github/workflows/ubuntu-check.yml index 58a064cf..88912934 100644 --- a/.github/workflows/ubuntu-check.yml +++ b/.github/workflows/ubuntu-check.yml @@ -17,8 +17,8 @@ jobs: # Don't prompt for anything export DEBIAN_FRONTEND=noninteractive sudo apt-get update - # openssl and nginx used for ocsp testing - sudo apt-get install -y openssl nginx + # openssl used for ocsp interop testing + sudo apt-get install -y openssl - uses: actions/checkout@master with: repository: wolfssl/wolfssl diff --git a/.gitignore b/.gitignore index 706eb8d5..21418665 100644 --- a/.gitignore +++ b/.gitignore @@ -43,5 +43,5 @@ AGENTS.md Win32/ x64/ .vs/ - +__pycache__/ CLAUDE.md diff --git a/Makefile.am b/Makefile.am index e63d8d9b..965bbdc4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,6 +19,9 @@ dist_doc_DATA= check_SCRIPTS= dist_noinst_SCRIPTS= +TEST_EXTENSIONS = .sh .py +PY_LOG_COMPILER = $(PYTHON) + #includes additional rules from aminclude.am @INC_AMINCLUDE@ DISTCLEANFILES+= aminclude.am @@ -61,6 +64,7 @@ include tests/hash/include.am include tests/bench/include.am include tests/client/include.am include tests/server/include.am +include tests/testEncDec/include.am include ide/include.am #####include data/include.am diff --git a/configure.ac b/configure.ac index 1512133c..f3b10e38 100644 --- a/configure.ac +++ b/configure.ac @@ -55,6 +55,8 @@ AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AM_PROG_CC_C_O +AM_PATH_PYTHON([3.0],, [:]) +AC_SUBST([PYTHON]) # Checks for headers/libraries AC_CHECK_HEADERS([sys/time.h string.h termios.h unistd.h]) diff --git a/ide/winvs/user_settings.h b/ide/winvs/user_settings.h index aff9cc3d..55ee2971 100644 --- a/ide/winvs/user_settings.h +++ b/ide/winvs/user_settings.h @@ -25,7 +25,18 @@ #define WOLFSSL_SHA512 #define HAVE_TLS_EXTENSIONS +#define HAVE_SNI +#define WOLFSSL_TLS13 +#define HAVE_HKDF +#define WC_RSA_PSS +#define HAVE_SUPPORTED_CURVES +#define HAVE_FFDHE_2048 #define OPENSSL_ALL #define OPENSSL_EXTRA +#define HAVE_PKCS7 +#define HAVE_PKCS12 +#define HAVE_CRL +#define HAVE_OCSP +#define HAVE_OCSP_RESPONDER #endif /* _WIN_USER_SETTINGS_H_ */ diff --git a/src/client/client.c b/src/client/client.c index 61fcf117..892edd72 100644 --- a/src/client/client.c +++ b/src/client/client.c @@ -2091,10 +2091,21 @@ static void Usage(void) #endif } -#ifndef USE_WINDOWS_API int checkStdin(void) { int stop = 0; +#ifdef USE_WINDOWS_API + HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); + if (stdinHandle == INVALID_HANDLE_VALUE || stdinHandle == NULL) { + stop = 1; /* no stdin available */ + } + else { + DWORD waitResult = WaitForSingleObject(stdinHandle, 0); + if (waitResult == WAIT_OBJECT_0) { + stop = 1; /* stdin has data or is closed */ + } + } +#else fd_set readfds; struct timeval timeout; timeout.tv_sec = 0; @@ -2107,11 +2118,9 @@ int checkStdin(void) if (select(1, &readfds, NULL, NULL, &timeout)){ stop = 1; } - +#endif return stop; - } -#endif static int AlwaysAllow(int preverify, WOLFSSL_X509_STORE_CTX* store) { @@ -4224,7 +4233,6 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) goto exit; } -#ifndef USE_WINDOWS_API if (!disable_stdin_chk) { int stop = checkStdin(); @@ -4234,7 +4242,6 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) goto exit; } } -#endif err = ClientRead(ssl, reply, sizeof(reply)-1, 1, "", exitWithRet); if (exitWithRet && (err != 0)) { diff --git a/src/client/clu_client_setup.c b/src/client/clu_client_setup.c index 14c38db1..3be36370 100644 --- a/src/client/clu_client_setup.c +++ b/src/client/clu_client_setup.c @@ -65,11 +65,12 @@ static const char caFileFlag[] = "-A"; static const char noClientCert[] = "-x"; static const char startTLSFlag[] = "-M"; static const char disableCRLFlag[] = "-C"; +static const char sniFlag[] = "-S"; int myoptind = 0; char* myoptarg = NULL; -#define MAX_CLIENT_ARGS 15 +#define MAX_CLIENT_ARGS 17 /* return WOLFCLU_SUCCESS on success */ static int _addClientArg(const char** args, const char* in, int* idx) @@ -193,6 +194,14 @@ int wolfCLU_Client(int argc, char** argv) &clientArgc); } } + + /* Set SNI hostname so modern servers accept the connection */ + if (ret == WOLFCLU_SUCCESS && host != NULL) { + ret = _addClientArg(clientArgv, sniFlag, &clientArgc); + if (ret == WOLFCLU_SUCCESS) { + ret = _addClientArg(clientArgv, host, &clientArgc); + } + } break; case WOLFCLU_STARTTLS: @@ -264,6 +273,7 @@ int wolfCLU_Client(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS) { + StartTCP(); args.argv = (char**)clientArgv; args.argc = clientArgc; diff --git a/src/ocsp/clu_ocsp.c b/src/ocsp/clu_ocsp.c index 4ca0dac9..90c2ee4e 100644 --- a/src/ocsp/clu_ocsp.c +++ b/src/ocsp/clu_ocsp.c @@ -1307,6 +1307,8 @@ int wolfCLU_OcspSetup(int argc, char** argv) return ret; } + StartTCP(); + if (!(isClientMode ^ isResponderMode)) { wolfCLU_LogError("Can't detect side (client vs responder) or multiple sides specified"); wolfCLU_OcspHelp(); diff --git a/src/server/clu_server_setup.c b/src/server/clu_server_setup.c index ae0eb126..9115839f 100644 --- a/src/server/clu_server_setup.c +++ b/src/server/clu_server_setup.c @@ -190,6 +190,7 @@ int wolfCLU_Server(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS) { + StartTCP(); args.argv = (char**)serverArgv; args.argc = serverArgc; server_test(&args); diff --git a/src/tools/clu_funcs.c b/src/tools/clu_funcs.c index f52d467e..1b5b4346 100644 --- a/src/tools/clu_funcs.c +++ b/src/tools/clu_funcs.c @@ -219,7 +219,8 @@ void wolfCLU_verboseHelp(void) #ifndef NO_AES WOLFCLU_LOG(WOLFCLU_L0, "aes-cbc-128\t\taes-cbc-192\t\taes-cbc-256"); #endif -#ifdef WOLFSSL_AES_COUNTER +#if defined(WOLFSSL_AES_COUNTER) && \ + LIBWOLFSSL_VERSION_HEX > 0x05009000 WOLFCLU_LOG(WOLFCLU_L0, "aes-ctr-128\t\taes-ctr-192\t\taes-ctr-256"); #endif #ifndef NO_DES3 @@ -253,7 +254,8 @@ void wolfCLU_encryptHelp(void) #ifndef NO_AES WOLFCLU_LOG(WOLFCLU_L0, "aes-cbc-128\t\taes-cbc-192\t\taes-cbc-256"); #endif -#ifdef WOLFSSL_AES_COUNTER +#if defined(WOLFSSL_AES_COUNTER) && \ + LIBWOLFSSL_VERSION_HEX > 0x05009000 WOLFCLU_LOG(WOLFCLU_L0, "aes-ctr-128\t\taes-ctr-192\t\taes-ctr-256"); #endif #ifndef NO_DES3 @@ -299,7 +301,8 @@ void wolfCLU_decryptHelp(void) #ifndef NO_AES WOLFCLU_LOG(WOLFCLU_L0, "aes-cbc-128\t\taes-cbc-192\t\taes-cbc-256"); #endif -#ifdef WOLFSSL_AES_COUNTER +#if defined(WOLFSSL_AES_COUNTER) && \ + LIBWOLFSSL_VERSION_HEX > 0x05009000 WOLFCLU_LOG(WOLFCLU_L0, "aes-ctr-128\t\taes-ctr-192\t\taes-ctr-256"); #endif #ifndef NO_DES3 diff --git a/tests/base64/base64-test.py b/tests/base64/base64-test.py new file mode 100644 index 00000000..261bbc34 --- /dev/null +++ b/tests/base64/base64-test.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Base64 encode/decode tests for wolfCLU.""" + +import filecmp +import os +import subprocess +import sys +import unittest + +# Allow importing the shared helper when run standalone or via the test runner +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl + + +class Base64Test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + # Skip if filesystem support is disabled (Linux autotools build) + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + # Skip if base64 coding support is not compiled in + result = run_wolfssl("base64", "-in", + os.path.join(CERTS_DIR, "server-key.der")) + combined = result.stdout + result.stderr + if "No coding support" in combined: + raise unittest.SkipTest("no base64 coding support") + + def test_encode(self): + """Encode server-key.der and verify output appears in server-key.pem.""" + result = run_wolfssl("base64", "-in", + os.path.join(CERTS_DIR, "server-key.der")) + self.assertEqual(result.returncode, 0, result.stderr) + + pem_path = os.path.join(CERTS_DIR, "server-key.pem") + with open(pem_path, "r") as f: + pem_contents = f.read() + + self.assertIn(result.stdout.strip(), pem_contents, + "server-key.der base64 conversion failed") + + def test_decode_and_reencode(self): + """Decode signed.p7s to DER, re-encode, and verify against original.""" + tmp_der = "testp7.der" + self.addCleanup(lambda: os.remove(tmp_der) + if os.path.exists(tmp_der) else None) + + result = run_wolfssl("base64", "-d", "-in", + os.path.join(CERTS_DIR, "signed.p7s"), + "-out", tmp_der) + self.assertEqual(result.returncode, 0, result.stderr) + + result = run_wolfssl("base64", "-in", tmp_der) + self.assertEqual(result.returncode, 0, result.stderr) + + p7s_path = os.path.join(CERTS_DIR, "signed.p7s") + with open(p7s_path, "r") as f: + p7s_contents = f.read() + + self.assertIn(result.stdout.strip(), p7s_contents, + "signed.p7s der base64 conversion failed") + + def test_roundtrip(self): + """Encode then decode server-key.der and verify files match.""" + encoded_file = "test-b64-encoded.b64" + decoded_file = "test-b64-decoded.der" + self.addCleanup(lambda: os.remove(encoded_file) + if os.path.exists(encoded_file) else None) + self.addCleanup(lambda: os.remove(decoded_file) + if os.path.exists(decoded_file) else None) + + original = os.path.join(CERTS_DIR, "server-key.der") + + result = run_wolfssl("base64", "-in", original, "-out", encoded_file) + self.assertEqual(result.returncode, 0, result.stderr) + + result = run_wolfssl("base64", "-d", "-in", encoded_file, + "-out", decoded_file) + self.assertEqual(result.returncode, 0, result.stderr) + + self.assertTrue(filecmp.cmp(original, decoded_file, shallow=False), + "base64 encode/decode round-trip failed") + + def test_stdin_input(self): + """Feed data via stdin and verify wolfssl processes it.""" + p7b_path = os.path.join(CERTS_DIR, "signed.p7b") + with open(p7b_path, "rb") as f: + stdin_data = f.read() + + result = subprocess.run( + [WOLFSSL_BIN, "base64"], + input=stdin_data, + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, + "Couldn't parse input from stdin") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/base64/base64-test.sh b/tests/base64/base64-test.sh deleted file mode 100755 index 0ca04f6c..00000000 --- a/tests/base64/base64-test.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -RESULT=`./wolfssl base64 -in certs/server-key.der 2>&1` -echo "$RESULT" | grep "No coding support" -if [ $? == 0 ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -run() { - RESULT=`./wolfssl $1` - - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -#test encode -run "base64 -in certs/server-key.der" - -if ! grep -F "$RESULT" certs/server-key.pem; then - echo "server-key.der base64 conversion failed" - exit 99 -fi - -#test decode -run "base64 -d -in certs/signed.p7s -out testp7.der" - -run "base64 -in testp7.der" - -if ! grep -F "$RESULT" certs/signed.p7s; then - echo "signed.p7s der base64 conversion failed" - exit 99 -fi - -rm -rf testp7.der - -# base64 encode round-trip test -run "base64 -in certs/server-key.der -out test-b64-encoded.b64" -run "base64 -d -in test-b64-encoded.b64 -out test-b64-decoded.der" -diff certs/server-key.der test-b64-decoded.der > /dev/null 2>&1 -if [ $? != 0 ]; then - echo "base64 encode/decode round-trip failed" - rm -f test-b64-encoded.b64 test-b64-decoded.der - exit 99 -fi -rm -f test-b64-encoded.b64 test-b64-decoded.der - -#check stdin input -RESULT=`cat certs/signed.p7b | ./wolfssl base64` -if [ $? != 0 ]; then - echo "Couldn't parse input from stdin" - exit 99 -fi - -echo "Done" -exit 0 diff --git a/tests/base64/include.am b/tests/base64/include.am index 143f48dd..b2142044 100644 --- a/tests/base64/include.am +++ b/tests/base64/include.am @@ -2,5 +2,5 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/base64/base64-test.sh +dist_noinst_SCRIPTS+=tests/base64/base64-test.py diff --git a/tests/bench/bench-test.py b/tests/bench/bench-test.py new file mode 100644 index 00000000..b49791f9 --- /dev/null +++ b/tests/bench/bench-test.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +"""Benchmark tests for wolfCLU.""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import run_wolfssl + + +class BenchTest(unittest.TestCase): + + def test_bench_aes_cbc(self): + result = run_wolfssl("-bench", "aes-cbc", "-time", "1") + self.assertEqual(result.returncode, 0, result.stderr) + + def test_bench_sha(self): + result = run_wolfssl("-bench", "sha", "-time", "1") + self.assertEqual(result.returncode, 0, result.stderr) + + def test_bench_md5(self): + result = run_wolfssl("-bench", "md5", "-time", "1") + self.assertEqual(result.returncode, 0, result.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/bench/bench-test.sh b/tests/bench/bench-test.sh deleted file mode 100755 index 7bc070f3..00000000 --- a/tests/bench/bench-test.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -run_success() { - RESULT=`./wolfssl $1` - if [ $? != 0 ]; then - echo "Failed on test \"$1\"" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - if [ $? == 0 ]; then - echo "Failed on test \"$1\"" - exit 99 - fi -} - -run_success "-bench aes-cbc -time 1" -run_success "-bench sha -time 1" -run_success "-bench md5 -time 1" - -echo "Done" -exit 0 diff --git a/tests/bench/include.am b/tests/bench/include.am index 0c5282b0..3800e309 100644 --- a/tests/bench/include.am +++ b/tests/bench/include.am @@ -2,6 +2,6 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/bench/bench-test.sh +dist_noinst_SCRIPTS+=tests/bench/bench-test.py diff --git a/tests/client/client-test.py b/tests/client/client-test.py new file mode 100644 index 00000000..c856970a --- /dev/null +++ b/tests/client/client-test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""TLS client tests for wolfCLU.""" + +import os +import subprocess +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl + + +class ClientTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + def test_s_client_x509(self): + """Connect to a TLS server, extract cert, and verify PEM output.""" + tmp_crt = "tmp.crt" + self.addCleanup(lambda: os.remove(tmp_crt) + if os.path.exists(tmp_crt) else None) + + # Run s_client with empty stdin so it connects then disconnects + s_client = subprocess.run( + [WOLFSSL_BIN, "s_client", "-connect", "www.google.com:443"], + input=b"\n", + capture_output=True, + timeout=30, + ) + + # Pipe s_client stdout into x509 to extract the cert as PEM + x509_extract = subprocess.run( + [WOLFSSL_BIN, "x509", "-outform", "pem", "-out", tmp_crt], + input=s_client.stdout, + capture_output=True, + timeout=60, + ) + + # Read back the cert + result = run_wolfssl("x509", "-in", tmp_crt) + self.assertIn("-----BEGIN CERTIFICATE-----", result.stdout, + "Expected x509 PEM output not found") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/client/client-test.sh b/tests/client/client-test.sh deleted file mode 100755 index 88297c2d..00000000 --- a/tests/client/client-test.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - - -echo | ./wolfssl s_client -connect www.google.com:443 | ./wolfssl x509 -outform pem -out tmp.crt - -RESULT=`./wolfssl x509 -in tmp.crt` - -echo $RESULT | grep -e "-----BEGIN CERTIFICATE-----" -if [ $? != 0 ]; then - echo "Expected x509 input not found" - exit 99 -fi - -rm tmp.crt - -echo "Done" -exit 0 diff --git a/tests/client/include.am b/tests/client/include.am index 534a5177..e9666028 100644 --- a/tests/client/include.am +++ b/tests/client/include.am @@ -2,6 +2,6 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/client/client-test.sh +dist_noinst_SCRIPTS+=tests/client/client-test.py diff --git a/tests/dgst/dgst-test.py b/tests/dgst/dgst-test.py new file mode 100644 index 00000000..a83fae0b --- /dev/null +++ b/tests/dgst/dgst-test.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +"""Digest sign/verify and large-file tests for wolfCLU.""" + +import filecmp +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, run_wolfssl + +DGST_DIR = os.path.join(".", "tests", "dgst") + + +class DgstVerifyTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + def test_verify_sha256_rsa(self): + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-signature", os.path.join(DGST_DIR, "sha256-rsa.sig"), + os.path.join(CERTS_DIR, "server-key.der")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_verify_md5_rsa(self): + r = run_wolfssl("dgst", "-md5", "-verify", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-signature", os.path.join(DGST_DIR, "md5-rsa.sig"), + os.path.join(CERTS_DIR, "server-key.der")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_verify_sha256_ecc(self): + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "ecc-keyPub.pem"), + "-signature", os.path.join(DGST_DIR, "sha256-ecc.sig"), + os.path.join(CERTS_DIR, "server-key.der")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_fail_ecc_key_rsa_sig(self): + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "ecc-keyPub.pem"), + "-signature", os.path.join(DGST_DIR, "sha256-rsa.sig"), + os.path.join(CERTS_DIR, "server-key.der")) + self.assertNotEqual(r.returncode, 0) + + def test_fail_wrong_ca_key(self): + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "ca-key.pem"), + "-signature", os.path.join(DGST_DIR, "sha256-rsa.sig"), + os.path.join(CERTS_DIR, "server-key.der")) + self.assertNotEqual(r.returncode, 0) + + def test_fail_private_key_as_verify(self): + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "server-key.pem"), + "-signature", os.path.join(DGST_DIR, "sha256-rsa.sig"), + os.path.join(CERTS_DIR, "server-key.der")) + self.assertNotEqual(r.returncode, 0) + + def test_fail_wrong_digest(self): + r = run_wolfssl("dgst", "-md5", "-verify", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-signature", os.path.join(DGST_DIR, "sha256-rsa.sig"), + os.path.join(CERTS_DIR, "server-key.der")) + self.assertNotEqual(r.returncode, 0) + + +class DgstLargeFileTest(unittest.TestCase): + + LARGE_FILE = "large-test.txt" + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + # Create large file: 5000 copies of server-key.der + der_path = os.path.join(CERTS_DIR, "server-key.der") + with open(der_path, "rb") as src: + chunk = src.read() + with open(cls.LARGE_FILE, "wb") as dst: + dst.write(chunk * 5000) + + @classmethod + def tearDownClass(cls): + for f in [cls.LARGE_FILE, "large-test.txt.enc", "large-test.txt.dec", + "5000-server-key.sig"]: + if os.path.exists(f): + os.remove(f) + + def test_verify_large_file(self): + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-signature", + os.path.join(DGST_DIR, "5000-server-key.sig"), + self.LARGE_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_sign_and_verify_large_file(self): + sig_file = "5000-server-key.sig" + self.addCleanup(lambda: os.remove(sig_file) + if os.path.exists(sig_file) else None) + + r = run_wolfssl("dgst", "-sha256", "-sign", + os.path.join(CERTS_DIR, "server-key.pem"), + "-out", sig_file, self.LARGE_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-signature", sig_file, self.LARGE_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_hash_large_file(self): + expected = "3e5915162b1974ac0d57a5a45113a1efcc1edc5e71e5e55ca69f9a7c60ca11fd" + + r = run_wolfssl("-hash", "sha256", "-in", self.LARGE_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn(expected, r.stdout, + "Failed to get expected hash with -hash") + + def test_sha256_large_file(self): + expected = "3e5915162b1974ac0d57a5a45113a1efcc1edc5e71e5e55ca69f9a7c60ca11fd" + + r = run_wolfssl("sha256", self.LARGE_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn(expected, r.stdout, + "Failed to get expected hash with sha256") + + # Slow when run inside a Windows VM (large file I/O over network share) + @unittest.skipIf(os.environ.get("WOLFCLU_SKIP_SLOW_TESTS", "0") == "1", + "slow test skipped via WOLFCLU_SKIP_SLOW_TESTS") + def test_enc_dec_large_file(self): + enc_file = "large-test.txt.enc" + dec_file = "large-test.txt.dec" + self.addCleanup(lambda: os.remove(enc_file) + if os.path.exists(enc_file) else None) + self.addCleanup(lambda: os.remove(dec_file) + if os.path.exists(dec_file) else None) + + r = run_wolfssl("enc", "-aes-256-cbc", "-in", self.LARGE_FILE, + "-out", enc_file, "-k", "12345678901234") + self.assertEqual(r.returncode, 0, r.stderr) + + self.assertFalse(filecmp.cmp(self.LARGE_FILE, enc_file, shallow=False), + "Encryption produced identical file") + + r = run_wolfssl("enc", "-d", "-aes-256-cbc", "-in", enc_file, + "-out", dec_file, "-k", "12345678901234") + self.assertEqual(r.returncode, 0, r.stderr) + + self.assertTrue(filecmp.cmp(self.LARGE_FILE, dec_file, shallow=False), + "Decryption of large file failed") + + +class DgstSignVerifyRoundtripTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + def test_ecc_sign_verify_roundtrip(self): + sig_file = "configure.sig" + # Use a file that exists on both Linux and Windows + input_file = os.path.join(CERTS_DIR, "server-key.der") + self.addCleanup(lambda: os.remove(sig_file) + if os.path.exists(sig_file) else None) + + r = run_wolfssl("dgst", "-sha256", "-sign", + os.path.join(CERTS_DIR, "ecc-key.pem"), + "-out", sig_file, input_file) + self.assertEqual(r.returncode, 0, r.stderr) + + # Verify with private key should fail + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "ecc-key.pem"), + "-signature", sig_file, input_file) + self.assertNotEqual(r.returncode, 0) + + # Verify with non-existent key should fail + r = run_wolfssl("dgst", "-sha256", "-verify", "bad-key.pem", + "-signature", sig_file, input_file) + self.assertNotEqual(r.returncode, 0) + + # Verify with wrong public key should fail + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-signature", sig_file, input_file) + self.assertNotEqual(r.returncode, 0) + + # Verify with correct public key should succeed + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "ecc-keyPub.pem"), + "-signature", sig_file, input_file) + self.assertEqual(r.returncode, 0, r.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/dgst/dgst-test.sh b/tests/dgst/dgst-test.sh deleted file mode 100755 index a03569d6..00000000 --- a/tests/dgst/dgst-test.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -run() { - RESULT=`./wolfssl $1` - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run "dgst -sha256 -verify ./certs/server-keyPub.pem -signature ./tests/dgst/sha256-rsa.sig ./certs/server-key.der" - -run "dgst -md5 -verify ./certs/server-keyPub.pem -signature ./tests/dgst/md5-rsa.sig ./certs/server-key.der" - -run "dgst -sha256 -verify ./certs/ecc-keyPub.pem -signature ./tests/dgst/sha256-ecc.sig ./certs/server-key.der" - -run_fail "dgst -sha256 -verify ./certs/ecc-keyPub.pem -signature ./tests/dgst/sha256-rsa.sig ./certs/server-key.der" - -run_fail "dgst -sha256 -verify ./certs/ca-key.pem -signature ./tests/dgst/sha256-rsa.sig ./certs/server-key.der" - -run_fail "dgst -sha256 -verify ./certs/server-key.pem -signature ./tests/dgst/sha256-rsa.sig ./certs/server-key.der" - -run_fail "dgst -md5 -verify ./certs/server-keyPub.pem -signature ./tests/dgst/sha256-rsa.sig ./certs/server-key.der" - - -echo "Doing large file test" -# recreate large file and test -rm -f large-test.txt -for i in {1..5000}; do - cat ./certs/server-key.der >> large-test.txt -done -run "dgst -sha256 -verify ./certs/server-keyPub.pem -signature ./tests/dgst/5000-server-key.sig ./large-test.txt" -run "dgst -sha256 -sign ./certs/server-key.pem -out 5000-server-key.sig ./large-test.txt" -run "dgst -sha256 -verify ./certs/server-keyPub.pem -signature ./5000-server-key.sig ./large-test.txt" -rm -rf 5000-server-key.sig - -# run some hash tests on large file while available -run "-hash sha256 -in ./large-test.txt" -echo $RESULT | grep "3e5915162b1974ac0d57a5a45113a1efcc1edc5e71e5e55ca69f9a7c60ca11fd" -if [ $? -ne 0 ]; then - echo "Failed to get expected hash of large file with -hash" - exit 99 -fi -run "sha256 ./large-test.txt" -echo $RESULT | grep "3e5915162b1974ac0d57a5a45113a1efcc1edc5e71e5e55ca69f9a7c60ca11fd" -if [ $? -ne 0 ]; then - echo "Failed to get expected hash of large file with sha256" - exit 99 -fi - -# run an enc/dec test on the large file -run "enc -aes-256-cbc -in ./large-test.txt -out large-test.txt.enc -k 12345678901234" -diff large-test.txt large-test.txt.enc &> /dev/null -if [ $? -eq 0 ]; then - echo "Encryption of large file failed" - exit 99 -fi - -run "enc -d -aes-256-cbc -in ./large-test.txt.enc -out large-test.txt.dec -k 12345678901234" -diff large-test.txt large-test.txt.dec -if [ $? -ne 0 ]; then - echo "Decryption of large file failed" - exit 99 -fi - -rm -f large-test.txt.enc -rm -f large-test.txt.dec -rm -f large-test.txt - -run "dgst -sha256 -sign ./certs/ecc-key.pem -out configure.sig configure.ac" -run_fail "dgst -sha256 -verify ./certs/ecc-key.pem -signature configure.sig configure.ac" -run_fail "dgst -sha256 -verify bad-key.pem -signature configure.sig configure.ac" -run_fail "dgst -sha256 -verify ./certs/server-keyPub.pem -signature configure.sig configure.ac" -run "dgst -sha256 -verify ./certs/ecc-keyPub.pem -signature configure.sig configure.ac" -rm -f configure.sig - -echo "Done" -exit 0 diff --git a/tests/dgst/include.am b/tests/dgst/include.am index 7febc597..de4b2b6a 100644 --- a/tests/dgst/include.am +++ b/tests/dgst/include.am @@ -2,5 +2,5 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/dgst/dgst-test.sh +dist_noinst_SCRIPTS+=tests/dgst/dgst-test.py diff --git a/tests/dh/dh-test.py b/tests/dh/dh-test.py new file mode 100644 index 00000000..7c40e839 --- /dev/null +++ b/tests/dh/dh-test.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""DH parameter tests for wolfCLU.""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, run_wolfssl + + +class DhParamTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + # Skip if DH not compiled in + r = run_wolfssl("dhparam", "1024") + combined = r.stdout + r.stderr + if "DH support not compiled into wolfSSL" in combined: + raise unittest.SkipTest("DH support not compiled in") + + def test_dhparam_stdout(self): + r = run_wolfssl("dhparam", "1024") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("-----BEGIN DH PARAMETERS-----", r.stdout) + + def test_dhparam_zero_fails(self): + r = run_wolfssl("dhparam", "0") + self.assertNotEqual(r.returncode, 0) + + def test_dhparam_out_and_in(self): + params_file = "dh.params" + self.addCleanup(lambda: os.remove(params_file) + if os.path.exists(params_file) else None) + + r = run_wolfssl("dhparam", "-out", params_file, "1024") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dhparam", "-in", params_file) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("-----BEGIN DH PARAMETERS-----", r.stdout) + + def test_dhparam_out_after_size(self): + """Test -out flag after the size argument.""" + params_file = "dh.params" + self.addCleanup(lambda: os.remove(params_file) + if os.path.exists(params_file) else None) + + r = run_wolfssl("dhparam", "1024", "-out", params_file) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dhparam", "-in", params_file) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("-----BEGIN DH PARAMETERS-----", r.stdout) + + def test_dhparam_noout(self): + params_file = "dh.params" + self.addCleanup(lambda: os.remove(params_file) + if os.path.exists(params_file) else None) + + r = run_wolfssl("dhparam", "1024", "-out", params_file) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dhparam", "-in", params_file, "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertNotIn("-----BEGIN DH PARAMETERS-----", r.stdout) + + def test_dhparam_genkey(self): + params_file = "dh.params" + self.addCleanup(lambda: os.remove(params_file) + if os.path.exists(params_file) else None) + + r = run_wolfssl("dhparam", "1024", "-out", params_file) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dhparam", "-in", params_file, "-genkey") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("-----BEGIN DH PARAMETERS-----", r.stdout) + self.assertIn("-----BEGIN PRIVATE KEY-----", r.stdout) + + def test_dhparam_genkey_noout(self): + params_file = "dh.params" + self.addCleanup(lambda: os.remove(params_file) + if os.path.exists(params_file) else None) + + r = run_wolfssl("dhparam", "1024", "-out", params_file) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dhparam", "-in", params_file, "-genkey", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertNotIn("-----BEGIN DH PARAMETERS-----", r.stdout) + self.assertIn("-----BEGIN PRIVATE KEY-----", r.stdout) + + def test_bad_input_fails(self): + r = run_wolfssl("dhparam", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-genkey", "-noout") + self.assertNotEqual(r.returncode, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/dh/dh-test.sh b/tests/dh/dh-test.sh deleted file mode 100755 index c35491f7..00000000 --- a/tests/dh/dh-test.sh +++ /dev/null @@ -1,111 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -run() { - RESULT=`./wolfssl $1` - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -# Test if DH compiled in -RESULT=`./wolfssl dhparam 1024 2>&1` -echo $RESULT | grep "DH support not compiled into wolfSSL" -if [ $? == 0 ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -run "dhparam 1024" -echo $RESULT | grep -e "-----BEGIN DH PARAMETERS-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -run_fail "dhparam 0" -run "dhparam -out dh.params 1024" -run "dhparam -in dh.params" -echo $RESULT | grep -e "-----BEGIN DH PARAMETERS-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi -rm -f dh.params - -run "dhparam 1024 -out dh.params" -run "dhparam -in dh.params" -echo $RESULT | grep -e "-----BEGIN DH PARAMETERS-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -#check for no output -run "dhparam -in dh.params -noout" -echo $RESULT | grep -e "-----BEGIN DH PARAMETERS-----" -if [ $? == 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -#generate a key -run "dhparam -in dh.params -genkey" -echo $RESULT | grep -e "-----BEGIN DH PARAMETERS-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi -echo $RESULT | grep -e "-----BEGIN PRIVATE KEY-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -#check noout with generate a key -run "dhparam -in dh.params -genkey -noout" -echo $RESULT | grep -e "-----BEGIN DH PARAMETERS-----" -if [ $? == 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi -echo $RESULT | grep -e "-----BEGIN PRIVATE KEY-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -#check bad input -run_fail "dhparam -in ./certs/server-cert.pem -genkey -noout" -rm -f dh.params - -echo "Done" -exit 0 diff --git a/tests/dh/include.am b/tests/dh/include.am index 71aeb14b..5389096a 100644 --- a/tests/dh/include.am +++ b/tests/dh/include.am @@ -2,6 +2,6 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/dh/dh-test.sh +dist_noinst_SCRIPTS+=tests/dh/dh-test.py diff --git a/tests/dsa/dsa-test.py b/tests/dsa/dsa-test.py new file mode 100644 index 00000000..b8e0eb79 --- /dev/null +++ b/tests/dsa/dsa-test.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""DSA parameter tests for wolfCLU.""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, run_wolfssl + + +class DsaParamTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + # Skip if DSA not compiled in + r = run_wolfssl("dsaparam", "1024") + combined = r.stdout + r.stderr + if "DSA support not compiled into wolfSSL" in combined: + raise unittest.SkipTest("DSA support not compiled in") + + def test_dsaparam_stdout(self): + r = run_wolfssl("dsaparam", "1024") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("-----BEGIN DSA PARAMETERS-----", r.stdout) + + def test_dsaparam_zero_fails(self): + r = run_wolfssl("dsaparam", "0") + self.assertNotEqual(r.returncode, 0) + + def test_dsaparam_out_and_in(self): + params_file = "dsa.params" + self.addCleanup(lambda: os.remove(params_file) + if os.path.exists(params_file) else None) + + r = run_wolfssl("dsaparam", "-out", params_file, "1024") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dsaparam", "-in", params_file) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("-----BEGIN DSA PARAMETERS-----", r.stdout) + + def test_dsaparam_noout(self): + params_file = "dsa.params" + self.addCleanup(lambda: os.remove(params_file) + if os.path.exists(params_file) else None) + + r = run_wolfssl("dsaparam", "-out", params_file, "1024") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dsaparam", "-in", params_file, "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertNotIn("-----BEGIN DSA PARAMETERS-----", r.stdout) + + def test_dsaparam_genkey(self): + params_file = "dsa.params" + self.addCleanup(lambda: os.remove(params_file) + if os.path.exists(params_file) else None) + + r = run_wolfssl("dsaparam", "-out", params_file, "1024") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dsaparam", "-in", params_file, "-genkey") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("-----BEGIN DSA PARAMETERS-----", r.stdout) + self.assertIn("-----BEGIN DSA PRIVATE KEY-----", r.stdout) + + def test_dsaparam_genkey_noout(self): + params_file = "dsa.params" + self.addCleanup(lambda: os.remove(params_file) + if os.path.exists(params_file) else None) + + r = run_wolfssl("dsaparam", "-out", params_file, "1024") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dsaparam", "-in", params_file, "-genkey", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertNotIn("-----BEGIN DSA PARAMETERS-----", r.stdout) + self.assertIn("-----BEGIN DSA PRIVATE KEY-----", r.stdout) + + def test_bad_input_fails(self): + r = run_wolfssl("dsaparam", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-genkey", "-noout") + self.assertNotEqual(r.returncode, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/dsa/dsa-test.sh b/tests/dsa/dsa-test.sh deleted file mode 100755 index a4d1d6f3..00000000 --- a/tests/dsa/dsa-test.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -run() { - RESULT=`./wolfssl $1` - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -# Test if DSA compiled in -RESULT=`./wolfssl dsaparam 1024 2>&1` -echo $RESULT | grep "DSA support not compiled into wolfSSL" -if [ $? == 0 ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -run "dsaparam 1024" -echo $RESULT | grep -e "-----BEGIN DSA PARAMETERS-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -run_fail "dsaparam 0" -run "dsaparam -out dsa.params 1024" -run "dsaparam -in dsa.params" -echo $RESULT | grep -e "-----BEGIN DSA PARAMETERS-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -#check for no output -run "dsaparam -in dsa.params -noout" -echo $RESULT | grep -e "-----BEGIN DSA PARAMETERS-----" -if [ $? == 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -#generate a key -run "dsaparam -in dsa.params -genkey" -echo $RESULT | grep -e "-----BEGIN DSA PARAMETERS-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi -echo $RESULT | grep -e "-----BEGIN DSA PRIVATE KEY-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -#check noout with generate a key -run "dsaparam -in dsa.params -genkey -noout" -echo $RESULT | grep -e "-----BEGIN DSA PARAMETERS-----" -if [ $? == 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi -echo $RESULT | grep -e "-----BEGIN DSA PRIVATE KEY-----" -if [ $? != 0 ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -#check bad input -run_fail "dsaparam -in ./certs/server-cert.pem -genkey -noout" -rm -f dsa.params - -echo "Done" -exit 0 - diff --git a/tests/dsa/include.am b/tests/dsa/include.am index 9e61c64b..20d471f1 100644 --- a/tests/dsa/include.am +++ b/tests/dsa/include.am @@ -2,6 +2,6 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/dsa/dsa-test.sh +dist_noinst_SCRIPTS+=tests/dsa/dsa-test.py diff --git a/tests/encrypt/enc-test.py b/tests/encrypt/enc-test.py new file mode 100644 index 00000000..9d02bd2f --- /dev/null +++ b/tests/encrypt/enc-test.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python3 +"""Encryption/decryption tests for wolfCLU.""" + +import filecmp +import os +import shutil +import subprocess +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, WOLFSSL_BIN, run_wolfssl + + +def run_enc(*args, password=""): + """Run wolfssl enc with a -k password argument appended.""" + cmd = [WOLFSSL_BIN] + list(args) + ["-k", password] + return subprocess.run(cmd, capture_output=True, text=True, + stdin=subprocess.DEVNULL, timeout=60) + + +class EncDecryptTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def test_decrypt_nosalt(self): + dec = "test-dec.der" + self._cleanup(dec) + + r = run_enc("enc", "-d", "-aes-256-cbc", "-nosalt", + "-in", os.path.join(CERTS_DIR, "crl.der.enc"), + "-out", dec, password="") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue( + filecmp.cmp(os.path.join(CERTS_DIR, "crl.der"), dec, shallow=False), + "decryption 1 mismatch") + + def test_decrypt_base64_nosalt(self): + dec = "test-dec.der" + self._cleanup(dec) + + r = run_enc("enc", "-base64", "-d", "-aes-256-cbc", "-nosalt", + "-in", os.path.join(CERTS_DIR, "crl.der.enc.base64"), + "-out", dec, password="") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue( + filecmp.cmp(os.path.join(CERTS_DIR, "crl.der"), dec, shallow=False), + "decryption 2 mismatch") + + def test_fail_nonexistent_file(self): + dec = "test-dec.der" + self._cleanup(dec) + + r = run_enc("enc", "-base64", "-d", "-aes-256-cbc", "-nosalt", + "-in", os.path.join(CERTS_DIR, "file-does-not-exist"), + "-out", dec, password="") + self.assertNotEqual(r.returncode, 0) + + def test_encrypt_decrypt_base64(self): + enc = "test-enc.der" + dec = "test-dec.der" + orig = os.path.join(CERTS_DIR, "crl.der") + self._cleanup(enc, dec) + + r = run_enc("enc", "-base64", "-aes-256-cbc", + "-in", orig, "-out", enc, + password="test password") + self.assertEqual(r.returncode, 0, r.stderr) + + # Bad password should fail + r = run_enc("enc", "-base64", "-d", "-aes-256-cbc", + "-in", enc, "-out", dec, + password="bad password") + self.assertNotEqual(r.returncode, 0) + + r = run_enc("enc", "-base64", "-d", "-aes-256-cbc", + "-in", enc, "-out", dec, + password="test password") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(orig, dec, shallow=False), + "decryption 3 mismatch") + + def test_aes128_roundtrip(self): + # Use a file that exists on both Linux and Windows + orig = os.path.join(CERTS_DIR, "server-key.der") + enc = "roundtrip.enc" + dec = "roundtrip.dec" + self._cleanup(enc, dec) + + r = run_enc("enc", "-aes-128-cbc", "-in", orig, "-out", enc, + password="test") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_enc("enc", "-d", "-aes-128-cbc", "-in", enc, "-out", dec, + password="test") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(orig, dec, shallow=False), + "decrypted file does not match original") + + def test_small_file(self): + small = "enc_small.txt" + enc = "enc_small.txt.enc" + dec = "enc_small.txt.dec" + self._cleanup(small, enc, dec) + + with open(small, "w") as f: + f.write(" \n") + + r = run_enc("enc", "-aes-128-cbc", "-in", small, "-out", enc, + password="test") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_enc("enc", "-d", "-aes-128-cbc", "-in", enc, "-out", dec, + password="test") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(small, dec, shallow=False), + "small file decryption mismatch") + + +class EncInteropTest(unittest.TestCase): + """Test interoperability with OpenSSL (skipped if openssl not available).""" + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + if shutil.which("openssl") is None: + raise unittest.SkipTest("openssl not found") + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def test_openssl_enc_wolfssl_dec(self): + enc = "test-enc.der" + dec = "test-dec.der" + orig = os.path.join(CERTS_DIR, "crl.der") + self._cleanup(enc, dec) + + ossl = subprocess.run(["openssl", "enc", "-base64", "-aes-256-cbc", + "-k", "test password", "-in", orig, "-out", enc], + capture_output=True, timeout=60) + self.assertEqual(ossl.returncode, 0, ossl.stderr) + + r = run_enc("enc", "-base64", "-d", "-aes-256-cbc", + "-in", enc, "-out", dec, password="test password") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(orig, dec, shallow=False)) + + def test_wolfssl_enc_openssl_dec(self): + enc = "test-enc.der" + dec = "test-dec.der" + orig = os.path.join(CERTS_DIR, "crl.der") + self._cleanup(enc, dec) + + r = run_enc("enc", "-base64", "-aes-256-cbc", + "-in", orig, "-out", enc, password="test password") + self.assertEqual(r.returncode, 0, r.stderr) + + ossl = subprocess.run(["openssl", "enc", "-base64", "-d", "-aes-256-cbc", + "-k", "test password", "-in", enc, "-out", dec], + capture_output=True, timeout=60) + self.assertEqual(ossl.returncode, 0, ossl.stderr) + self.assertTrue(filecmp.cmp(orig, dec, shallow=False)) + + def test_pbkdf2_openssl_enc_wolfssl_dec(self): + enc = "test-enc.der" + dec = "test-dec.der" + orig = os.path.join(CERTS_DIR, "crl.der") + self._cleanup(enc, dec) + + ossl = subprocess.run(["openssl", "enc", "-base64", "-pbkdf2", "-aes-256-cbc", + "-k", "long test password", "-in", orig, "-out", enc], + capture_output=True, timeout=60) + self.assertEqual(ossl.returncode, 0, ossl.stderr) + + r = run_enc("enc", "-base64", "-d", "-pbkdf2", "-aes-256-cbc", + "-in", enc, "-out", dec, password="long test password") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(orig, dec, shallow=False)) + + def test_pbkdf2_wolfssl_enc_openssl_dec(self): + enc = "test-enc.der" + dec = "test-dec.der" + orig = os.path.join(CERTS_DIR, "crl.der") + self._cleanup(enc, dec) + + r = run_enc("enc", "-base64", "-pbkdf2", "-aes-256-cbc", + "-in", orig, "-out", enc, password="long test password") + self.assertEqual(r.returncode, 0, r.stderr) + + ossl = subprocess.run(["openssl", "enc", "-base64", "-d", "-pbkdf2", + "-aes-256-cbc", "-k", "long test password", + "-in", enc, "-out", dec], + capture_output=True, timeout=60) + self.assertEqual(ossl.returncode, 0, ossl.stderr) + self.assertTrue(filecmp.cmp(orig, dec, shallow=False)) + + def test_pbkdf2_wolfssl_pass_flag(self): + enc = "test-enc.der" + dec = "test-dec.der" + orig = os.path.join(CERTS_DIR, "crl.der") + self._cleanup(enc, dec) + + r = run_enc("enc", "-base64", "-pbkdf2", "-aes-256-cbc", + "-in", orig, "-out", enc, password="long test password") + self.assertEqual(r.returncode, 0, r.stderr) + + # Decrypt using -pass flag instead of -k + r = subprocess.run( + [WOLFSSL_BIN, "enc", "-base64", "-d", "-pbkdf2", "-aes-256-cbc", + "-pass", "pass:long test password", "-in", enc, "-out", dec], + capture_output=True, text=True, stdin=subprocess.DEVNULL, + timeout=60) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(orig, dec, shallow=False)) + + +class EncLegacyNamesTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def _roundtrip(self, enc_algo, dec_algo, msg): + enc = "test-enc.der" + dec = "test-dec.der" + orig = os.path.join(CERTS_DIR, "crl.der") + self._cleanup(enc, dec) + + r = run_enc("enc", enc_algo, "-in", orig, "-out", enc, + password="test password") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_enc("enc", "-d", dec_algo, "-in", enc, "-out", dec, + password="test password") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(orig, dec, shallow=False), msg) + + def test_legacy_aes_cbc_256_roundtrip(self): + self._roundtrip("-aes-cbc-256", "-aes-cbc-256", + "legacy aes-cbc-256 round trip failed") + + def test_legacy_enc_canonical_dec(self): + self._roundtrip("-aes-cbc-256", "-aes-256-cbc", + "legacy enc / canonical dec failed") + + def test_canonical_enc_legacy_dec(self): + self._roundtrip("-aes-256-cbc", "-aes-cbc-256", + "canonical enc / legacy dec failed") + + def test_legacy_aes_cbc_128_roundtrip(self): + self._roundtrip("-aes-cbc-128", "-aes-cbc-128", + "legacy aes-cbc-128 round trip failed") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/encrypt/enc-test.sh b/tests/encrypt/enc-test.sh deleted file mode 100755 index 31b7754f..00000000 --- a/tests/encrypt/enc-test.sh +++ /dev/null @@ -1,189 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -run() { - RESULT=`./wolfssl $1 -k "$2"` - if [ $? != 0 ]; then - echo "Failed on test \"$1\"" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1 -k "$2"` - if [ $? == 0 ]; then - echo "Failed on test \"$1\"" - exit 99 - fi -} - - -run "enc -d -aes-256-cbc -nosalt -in certs/crl.der.enc -out test-dec.der" "" -diff "./certs/crl.der" "./test-dec.der" &> /dev/null -if [ $? != 0 ]; then - echo "issue with decryption 1" - exit 99 -fi -rm -f test-dec.der - -run "enc -base64 -d -aes-256-cbc -nosalt -in certs/crl.der.enc.base64 -out test-dec.der" "" -diff "./certs/crl.der" "./test-dec.der" &> /dev/null -if [ $? != 0 ]; then - echo "issue with decryption 2" - exit 99 -fi -rm -f test-dec.der - - -# check fail cases -run_fail "enc -base64 -d -aes-256-cbc -nosalt -in certs/file-does-not-exist -out test-dec.der" "" - - -# encrypt and then test decrypt -run "enc -base64 -aes-256-cbc -in certs/crl.der -out test-enc.der" "test password" -run_fail "enc -base64 -d -aes-256-cbc -in test-enc.der -out test-dec.der" "bad password" -run "enc -base64 -d -aes-256-cbc -in test-enc.der -out test-dec.der" "test password" -diff "./certs/crl.der" "./test-dec.der" &> /dev/null -if [ $? != 0 ]; then - echo "issue with decryption 3" - exit 99 -fi -rm -f test-dec.der -rm -f test-enc.der - -run "enc -aes-128-cbc -in ./configure.ac -out ./configure.ac.enc" "test" -run "enc -d -aes-128-cbc -in ./configure.ac.enc -out ./configure.ac.dec" "test" -diff ./configure.ac ./configure.ac.dec -if [ $? != 0 ]; then - echo "decrypted file does not match original file" - exit 99 -fi -rm -f configure.ac.dec -rm -f configure.ac.enc - -# small file test -rm -rf enc_small.txt -echo " " > enc_small.txt -run "enc -aes-128-cbc -in ./enc_small.txt -out ./enc_small.txt.enc 'test'" -run "enc -d -aes-128-cbc -in ./enc_small.txt.enc -out ./enc_small.txt.dec 'test'" -diff ./enc_small.txt ./enc_small.txt.dec -if [ $? != 0 ]; then - echo "enc_small decrypted file does not match original file" - exit 99 -fi -rm -f enc_small.txt -rm -f enc_small.txt.dec -rm -f enc_small.txt.enc - -# interoperability testing -openssl enc --help &> /dev/null -if [ $? == 0 ]; then - openssl enc -base64 -aes-256-cbc -k 'test password' -in certs/crl.der -out test-enc.der &> /dev/null - run "enc -base64 -d -aes-256-cbc -in test-enc.der -out test-dec.der" "test password" - diff "./certs/crl.der" "./test-dec.der" &> /dev/null - if [ $? != 0 ]; then - echo "issue openssl enc and wolfssl dec" - exit 99 - fi - rm -f test-dec.der - rm -f test-enc.der - - run "enc -base64 -aes-256-cbc -in certs/crl.der -out test-enc.der" "test password" - openssl enc -base64 -d -aes-256-cbc -k 'test password' -in test-enc.der -out test-dec.der &> /dev/null - diff "./certs/crl.der" "./test-dec.der" &> /dev/null - if [ $? != 0 ]; then - echo "issue wolfssl enc and openssl dec" - exit 99 - fi - rm -f test-dec.der - rm -f test-enc.der - - # now try with -pbkdf2 - openssl enc -base64 -pbkdf2 -aes-256-cbc -k 'long test password' -in certs/crl.der -out test-enc.der &> /dev/null - run "enc -base64 -d -pbkdf2 -aes-256-cbc -in test-enc.der -out test-dec.der" "long test password" - diff "./certs/crl.der" "./test-dec.der" &> /dev/null - if [ $? != 0 ]; then - echo "issue openssl enc and wolfssl dec pbkdf2" - exit 99 - fi - rm -f test-dec.der - rm -f test-enc.der - - run "enc -base64 -pbkdf2 -aes-256-cbc -in certs/crl.der -out test-enc.der" "long test password" - openssl enc -base64 -d -pbkdf2 -aes-256-cbc -k 'long test password' -in test-enc.der -out test-dec.der &> /dev/null - diff "./certs/crl.der" "./test-dec.der" &> /dev/null - if [ $? != 0 ]; then - echo "issue wolfssl enc and openssl dec pbkdf2" - exit 99 - fi - ./wolfssl enc -base64 -d -pbkdf2 -aes-256-cbc -pass 'pass:long test password' -in test-enc.der -out test-dec.der - if [ $? != 0 ]; then - echo "issue wolfssl decrypt using -pass" - exit 99 - fi - diff "./certs/crl.der" "./test-dec.der" &> /dev/null - if [ $? != 0 ]; then - echo "issue wolfssl -pass decrypt mismatch" - exit 99 - fi - rm -f test-dec.der - rm -f test-enc.der -fi - -# test legacy algo names -run "enc -base64 -aes-cbc-256 -in certs/crl.der -out test-enc.der" "test password" -run "enc -base64 -d -aes-cbc-256 -in test-enc.der -out test-dec.der" "test password" -diff "./certs/crl.der" "./test-dec.der" &> /dev/null -if [ $? != 0 ]; then - echo "issue with legacy name aes-cbc-256 round trip" - exit 99 -fi -rm -f test-dec.der -rm -f test-enc.der - -# encrypt with legacy name, decrypt with canonical name -run "enc -aes-cbc-256 -in certs/crl.der -out test-enc.der" "test password" -run "enc -d -aes-256-cbc -in test-enc.der -out test-dec.der" "test password" -diff "./certs/crl.der" "./test-dec.der" &> /dev/null -if [ $? != 0 ]; then - echo "issue with legacy enc / canonical dec" - exit 99 -fi -rm -f test-dec.der -rm -f test-enc.der - -# encrypt with canonical name, decrypt with legacy name -run "enc -aes-256-cbc -in certs/crl.der -out test-enc.der" "test password" -run "enc -d -aes-cbc-256 -in test-enc.der -out test-dec.der" "test password" -diff "./certs/crl.der" "./test-dec.der" &> /dev/null -if [ $? != 0 ]; then - echo "issue with canonical enc / legacy dec" - exit 99 -fi -rm -f test-dec.der -rm -f test-enc.der - -# test legacy name with aes-cbc-128 -run "enc -aes-cbc-128 -in certs/crl.der -out test-enc.der" "test password" -run "enc -d -aes-cbc-128 -in test-enc.der -out test-dec.der" "test password" -diff "./certs/crl.der" "./test-dec.der" &> /dev/null -if [ $? != 0 ]; then - echo "issue with legacy name aes-cbc-128 round trip" - exit 99 -fi -rm -f test-dec.der -rm -f test-enc.der - -echo "Done" -exit 0 diff --git a/tests/encrypt/include.am b/tests/encrypt/include.am index aa60cd6b..ea0913c3 100644 --- a/tests/encrypt/include.am +++ b/tests/encrypt/include.am @@ -2,6 +2,6 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/encrypt/enc-test.sh +dist_noinst_SCRIPTS+=tests/encrypt/enc-test.py diff --git a/tests/genkey_sign_ver/genkey-sign-ver-test.py b/tests/genkey_sign_ver/genkey-sign-ver-test.py new file mode 100644 index 00000000..de176571 --- /dev/null +++ b/tests/genkey_sign_ver/genkey-sign-ver-test.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +"""Key generation, signing, and verification tests for wolfCLU.""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, run_wolfssl + +# Files that tests may create; cleaned up by tearDownClass +_TEMP_FILES = [] + + +def _cleanup_files(files): + for f in files: + if os.path.exists(f): + os.remove(f) + + +def _has_algorithm(algo): + """Check if an algorithm is available in the current build.""" + r = run_wolfssl("-genkey", "-h") + combined = r.stdout + r.stderr + # Look for the algorithm name in the help output + return algo in combined + + +class _GenkeySignVerifyBase(unittest.TestCase): + """Base class with the gen-key / sign / verify workflow.""" + + SIGN_FILE = "sign-this.txt" + + @classmethod + def setUpClass(cls): + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + with open(cls.SIGN_FILE, "w") as f: + f.write("Sign this data\n") + + @classmethod + def tearDownClass(cls): + _cleanup_files([cls.SIGN_FILE] + _TEMP_FILES) + _TEMP_FILES.clear() + + def _track(self, *files): + for f in files: + _TEMP_FILES.append(f) + + def _genkey(self, algo, keybase, fmt, extra_args=None, + use_output_flag=False): + args = ["-genkey", algo] + if extra_args: + args += extra_args + args += ["-out", keybase, "-outform", fmt] + if use_output_flag: + args += ["-output", "KEYPAIR"] + else: + args.append("KEYPAIR") + priv = keybase + ".priv" + pub = keybase + ".pub" + self._track(priv, pub) + r = run_wolfssl(*args) + self.assertEqual(r.returncode, 0, + f"genkey {algo} failed: {r.stderr}") + return priv, pub + + def _sign(self, algo, priv_key, fmt, sig_file): + self._track(sig_file) + r = run_wolfssl(f"-{algo}", "-sign", "-inkey", priv_key, + "-inform", fmt, "-in", self.SIGN_FILE, + "-out", sig_file) + self.assertEqual(r.returncode, 0, + f"sign {algo} failed: {r.stderr}") + + def _verify_priv(self, algo, priv_key, fmt, sig_file, out_file=None): + args = [f"-{algo}", "-verify", "-inkey", priv_key, + "-inform", fmt, "-sigfile", sig_file, "-in", self.SIGN_FILE] + if out_file: + args += ["-out", out_file] + self._track(out_file) + r = run_wolfssl(*args) + self.assertEqual(r.returncode, 0, + f"private verify {algo} failed: {r.stderr}") + + def _verify_pub(self, algo, pub_key, fmt, sig_file, out_file=None): + args = [f"-{algo}", "-verify", "-inkey", pub_key, + "-inform", fmt, "-sigfile", sig_file, "-in", self.SIGN_FILE, + "-pubin"] + if out_file: + args += ["-out", out_file] + self._track(out_file) + r = run_wolfssl(*args) + self.assertEqual(r.returncode, 0, + f"public verify {algo} failed: {r.stderr}") + + def _gen_sign_verify(self, algo, keybase, sig_file, fmt, + extra_genkey_args=None, skip_priv_verify=False, + rsa_verify_out=None, use_output_flag=False): + priv, pub = self._genkey(algo, keybase, fmt, extra_genkey_args, + use_output_flag=use_output_flag) + self._sign(algo, priv, fmt, sig_file) + + if not skip_priv_verify: + priv_out = rsa_verify_out + ".private_result" if rsa_verify_out else None + self._verify_priv(algo, priv, fmt, sig_file, priv_out) + + pub_out = rsa_verify_out + ".public_result" if rsa_verify_out else None + self._verify_pub(algo, pub, fmt, sig_file, pub_out) + + if rsa_verify_out: + with open(self.SIGN_FILE, "r") as f: + original = f.read() + for suffix in [".private_result", ".public_result"]: + result_file = rsa_verify_out + suffix + self._track(result_file) + with open(result_file, "r") as f: + decrypted = f.read() + self.assertEqual(decrypted, original, + f"RSA decrypted {suffix} mismatch") + + +class Ed25519Test(_GenkeySignVerifyBase): + + def test_ed25519_der(self): + self._gen_sign_verify("ed25519", "edkey", "ed-signed.sig", "der") + + def test_ed25519_pem(self): + self._gen_sign_verify("ed25519", "edkey", "ed-signed.sig", "pem") + + def test_ed25519_raw(self): + self._gen_sign_verify("ed25519", "edkey", "ed-signed.sig", "raw") + + +class EccTest(_GenkeySignVerifyBase): + + def test_ecc_der(self): + self._gen_sign_verify("ecc", "ecckey", "ecc-signed.sig", "der") + + def test_ecc_pem(self): + self._gen_sign_verify("ecc", "ecckey", "ecc-signed.sig", "pem") + + +class RsaTest(_GenkeySignVerifyBase): + + def test_rsa_der(self): + self._gen_sign_verify("rsa", "rsakey", "rsa-signed.sig", "der", + rsa_verify_out="rsa-sigout") + + def test_rsa_pem(self): + self._gen_sign_verify("rsa", "rsakey", "rsa-signed.sig", "pem", + rsa_verify_out="rsa-sigout") + + def test_rsa_bad_verify(self): + """Verify with invalid signature must fail gracefully.""" + priv, pub = self._genkey("rsa", "rsakey", "der") + bad_out = "rsa_badverify_out.txt" + self._track(bad_out) + + r = run_wolfssl("-rsa", "-verify", "-inkey", pub, "-inform", "der", + "-sigfile", self.SIGN_FILE, "-in", self.SIGN_FILE, + "-out", bad_out, "-pubin") + self.assertNotEqual(r.returncode, 0, + "RSA verify with invalid sig should have failed") + self.assertFalse(os.path.exists(bad_out), + "output file must not be created on bad verify") + + def test_rsa_exponent_flag(self): + """Regression: -exponent must not overwrite -size.""" + priv = "rsakey_exp.priv" + pub = "rsakey_exp.pub" + self._track(priv, pub) + + r = run_wolfssl("-genkey", "rsa", "-size", "2048", "-exponent", + "65537", "-out", "rsakey_exp", "-outform", "der", + "-output", "KEYPAIR") + self.assertEqual(r.returncode, 0, + f"rsa genkey with -exponent failed: {r.stderr}") + + +@unittest.skipUnless(_has_algorithm("dilithium"), + "dilithium not available") +class DilithiumTest(_GenkeySignVerifyBase): + + def test_dilithium_der(self): + for level in [2, 3, 5]: + with self.subTest(level=level): + self._gen_sign_verify( + "dilithium", "mldsakey", "mldsa-signed.sig", "der", + extra_genkey_args=["-level", str(level)], + skip_priv_verify=True, use_output_flag=True) + + def test_dilithium_pem(self): + for level in [2, 3, 5]: + with self.subTest(level=level): + self._gen_sign_verify( + "dilithium", "mldsakey", "mldsa-signed.sig", "pem", + extra_genkey_args=["-level", str(level)], + skip_priv_verify=True, use_output_flag=True) + + def test_output_pub_only(self): + pub = "mldsakey_pub.pub" + priv = "mldsakey_pub.priv" + self._track(pub, priv) + + r = run_wolfssl("-genkey", "dilithium", "-level", "2", + "-out", "mldsakey_pub", "-outform", "der", + "-output", "pub") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(os.path.exists(pub), ".pub file missing") + self.assertFalse(os.path.exists(priv), ".priv unexpectedly created") + + def test_output_priv_only(self): + pub = "mldsakey_priv.pub" + priv = "mldsakey_priv.priv" + self._track(pub, priv) + + r = run_wolfssl("-genkey", "dilithium", "-level", "2", + "-out", "mldsakey_priv", "-outform", "der", + "-output", "priv") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(os.path.exists(priv), ".priv file missing") + self.assertFalse(os.path.exists(pub), ".pub unexpectedly created") + + def test_sign_bad_path(self): + priv, pub = self._genkey("dilithium", "mldsakey", "der", + ["-level", "2"]) + bad_path = os.path.join("nonexistent_dir", "mldsa_bad.sig") + r = run_wolfssl("-dilithium", "-sign", "-inkey", priv, + "-inform", "der", "-in", self.SIGN_FILE, + "-out", bad_path) + self.assertNotEqual(r.returncode, 0, + "sign to invalid path should have failed") + + +@unittest.skipUnless(_has_algorithm("xmss"), "xmss not available") +class XmssTest(_GenkeySignVerifyBase): + + def test_xmss_raw(self): + keybase = "XMSS-SHA2_10_256" + self._track(keybase + ".priv", keybase + ".pub") + self._gen_sign_verify( + "xmss", keybase, "xmss-signed.sig", "raw", + extra_genkey_args=["-height", "10"], + skip_priv_verify=True, use_output_flag=True) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/genkey_sign_ver/genkey-sign-ver-test.sh b/tests/genkey_sign_ver/genkey-sign-ver-test.sh deleted file mode 100755 index 53239bd1..00000000 --- a/tests/genkey_sign_ver/genkey-sign-ver-test.sh +++ /dev/null @@ -1,320 +0,0 @@ -#!/bin/sh - -# genkey-sign-ver-test.sh -# -# Copyright (C) 2006-2025 wolfSSL Inc. -# -# This file is part of wolfSSL. -# -# wolfSSL is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# wolfSSL is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA -#/ -#/ -# - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -cleanup_genkey_sign_ver(){ - rm -f ecckey - rm ecckey.priv - rm ecckey.pub - rm edkey.priv - rm edkey.pub - rm rsakey.priv - rm rsakey.pub - rm mldsakey.priv - rm mldsakey.pub - rm mldsakey_pub.pub - rm mldsakey_pub.priv - rm mldsakey_priv.pub - rm mldsakey_priv.priv - rm ecc-signed.sig - rm ed-signed.sig - rm rsa-signed.sig - rm rsa-sigout.private_result - rm rsa-sigout.public_result - rm mldsa-signed.sig - rm xmss-signed.sig - # rm xmssmt-signed.sig - rm sign-this.txt - - # XMSS/XMSS^MT key files - rm XMSS-SHA2_10_256.priv - rm XMSS-SHA2_10_256.pub - # rm XMSS-SHA2_16_256.priv - # rm XMSS-SHA2_16_256.pub - # rm XMSS-SHA2_20_256.priv - # rm XMSS-SHA2_20_256.pub - - # rm XMSSMT-SHA2_20-2_256.priv - # rm XMSSMT-SHA2_20-2_256.pub - # rm XMSSMT-SHA2_20-4_256.priv - # rm XMSSMT-SHA2_20-4_256.pub - # rm XMSSMT-SHA2_40-2_256.priv - # rm XMSSMT-SHA2_40-2_256.pub - # rm XMSSMT-SHA2_40-4_256.priv - # rm XMSSMT-SHA2_40-4_256.pub - # rm XMSSMT-SHA2_40-8_256.priv - # rm XMSSMT-SHA2_40-8_256.pub - # rm XMSSMT-SHA2_60-3_256.priv - # rm XMSSMT-SHA2_60-3_256.pub - # rm XMSSMT-SHA2_60-6_256.priv - # rm XMSSMT-SHA2_60-6_256.pub - # rm XMSSMT-SHA2_60-12_256.priv - # rm XMSSMT-SHA2_60-12_256.pub -} -trap cleanup_genkey_sign_ver INT TERM EXIT - -create_sign_data_file(){ - printf '%s\n' "Sign this data" > sign-this.txt -} - -rsa_compare_decrypted(){ - if [ "${1}" = "${2}" ]; then - printf '%s\n' "Decrypted matches original, success!" - printf '%s\n' "DECRYPTED --> ${1}" - printf '%s\n' "ORIGINAL --> ${2}" - else - printf '%s\n' "Decrypted mismatch with original, FAILURE!" - printf '%s\n' "DECRYPTED --> ${1}" - printf '%s\n' "ORIGINAL --> ${2}" && exit 99 - fi -} - -gen_key_sign_ver_test(){ - - # generate a key pair for signing - if [ $1 = "dilithium" ]; then - ./wolfssl -genkey $1 -level $5 -out $2 -outform $4 -output KEYPAIR - elif [ $1 = "xmss" ]; then - ./wolfssl -genkey $1 -height $5 -out $2 -outform $4 -output KEYPAIR - elif [ $1 = "xmssmt" ]; then - ./wolfssl -genkey $1 -height $5 -layer $6 -out $2 -outform $4 -output KEYPAIR - else - ./wolfssl -genkey $1 -out $2 -outform $4 KEYPAIR - fi - RESULT=$? - printf '%s\n' "genkey RESULT - $RESULT" - [ $RESULT -ne 0 ] && printf '%s\n' "Failed $1 genkey" && \ - printf '%s\n' "Before running this test please configure wolfssl with" && \ - printf '%s\n' "--enable-keygen" && exit 99 - - # test signing with priv key - ./wolfssl -$1 -sign -inkey $2.priv -inform $4 -in sign-this.txt -out $3 - RESULT=$? - printf '%s\n' "sign RESULT - $RESULT" - [ $RESULT -ne 0 ] && printf '%s\n' "Failed $1 sign" && exit 99 - - # test verifying with priv key - if [ "${1}" = "rsa" ]; then - ./wolfssl -$1 -verify -inkey $2.priv -inform $4 -sigfile $3 -in sign-this.txt \ - -out $5.private_result - elif [ "${1}" = "dilithium" ] || [ "${1}" = "xmss" ] || [ "${1}" = "xmssmt" ]; then - # ./wolfssl -$1 -verify -inkey $2.priv -inform $4 -sigfile $3 -in sign-this.txt - # pass - : - else - ./wolfssl -$1 -verify -inkey $2.priv -inform $4 -sigfile $3 -in sign-this.txt - fi - RESULT=$? - printf '%s\n' "private verify RESULT - $RESULT" - [ $RESULT -ne 0 ] && printf '%s\n' "Failed $1 private verify" && exit 99 - - # test verifying with pub key - if [ "${1}" = "rsa" ]; then - ./wolfssl -$1 -verify -inkey $2.pub -inform $4 -sigfile $3 -in sign-this.txt \ - -out $5.public_result -pubin - else - ./wolfssl -$1 -verify -inkey $2.pub -inform $4 -sigfile $3 -in sign-this.txt -pubin - fi - RESULT=$? - printf '%s\n' "public verify RESULT - $RESULT" - [ $RESULT -ne 0 ] && printf '%s\n' "Failed $1 public verify " && exit 99 - - if [ $1 = "rsa" ]; then - ORIGINAL=`cat -A sign-this.txt` - - DECRYPTED=`cat -A $5.private_result` - rsa_compare_decrypted "${DECRYPTED}" "${ORIGINAL}" - - DECRYPTED=`cat -A $5.public_result` - rsa_compare_decrypted "${DECRYPTED}" "${ORIGINAL}" - fi - -} - -create_sign_data_file - -ALGORITHM="ed25519" -KEYFILENAME="edkey" -SIGOUTNAME="ed-signed.sig" -DERPEMRAW="der" -gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} - -ALGORITHM="ecc" -KEYFILENAME="ecckey" -SIGOUTNAME="ecc-signed.sig" -DERPEMRAW="der" -gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} - -ALGORITHM="rsa" -KEYFILENAME="rsakey" -SIGOUTNAME="rsa-signed.sig" -DERPEMRAW="der" -VERIFYOUTNAME="rsa-sigout" -gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} ${VERIFYOUTNAME} - -# A verify with invalid signature must fail gracefully. -./wolfssl -rsa -verify -inkey rsakey.pub -inform der \ - -sigfile sign-this.txt -in sign-this.txt \ - -out rsa_badverify_out.txt -pubin -RESULT=$? -[ $RESULT -eq 0 ] && \ - printf '%s\n' "RSA verify with invalid sig should have failed" && exit 99 -[ -f rsa_badverify_out.txt ] && \ - printf '%s\n' "RSA verify with invalid sig: output file must not be created" && exit 99 - -# Regression test: -exponent value must not overwrite -size (was stored in -# sizeArg instead of expArg, corrupting the key size). -./wolfssl -genkey rsa -size 2048 -exponent 65537 -out rsakey_exp \ - -outform der -output KEYPAIR -RESULT=$? -[ $RESULT -ne 0 ] && printf '%s\n' "Failed rsa genkey with explicit -exponent" && exit 99 -rm -f rsakey_exp.priv rsakey_exp.pub - -ALGORITHM="ed25519" -KEYFILENAME="edkey" -SIGOUTNAME="ed-signed.sig" -DERPEMRAW="pem" -gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} - -ALGORITHM="ecc" -KEYFILENAME="ecckey" -SIGOUTNAME="ecc-signed.sig" -DERPEMRAW="pem" -gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} - -ALGORITHM="rsa" -KEYFILENAME="rsakey" -SIGOUTNAME="rsa-signed.sig" -DERPEMRAW="pem" -VERIFYOUTNAME="rsa-sigout" -gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} ${VERIFYOUTNAME} - -ALGORITHM="ed25519" -KEYFILENAME="edkey" -SIGOUTNAME="ed-signed.sig" -DERPEMRAW="raw" -gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} - -if ./wolfssl -genkey -h 2>&1 | grep -A6 "Available keys with current configure" | grep dilithium; then - ALGORITHM="dilithium" - KEYFILENAME="mldsakey" - SIGOUTNAME="mldsa-signed.sig" - DERPEMRAW="der" - for level in 2 3 5 - do - gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} ${level} - done - -ALGORITHM="dilithium" -KEYFILENAME="mldsakey" -SIGOUTNAME="mldsa-signed.sig" -DERPEMRAW="pem" -for level in 2 3 5 -do - gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} ${level} -done - -# Verifies that -output PUB generates only the public key file. -./wolfssl -genkey dilithium -level 2 -out mldsakey_pub -outform der -output pub -RESULT=$? -[ $RESULT -ne 0 ] && printf '%s\n' "Failed dilithium genkey -output PUB" && exit 99 -[ ! -f mldsakey_pub.pub ] && printf '%s\n' "dilithium -output PUB: .pub file missing" && exit 99 -[ -f mldsakey_pub.priv ] && printf '%s\n' "dilithium -output PUB: .priv unexpectedly created" && exit 99 - -# Verifies that -output PRIV generates only the private key file. -./wolfssl -genkey dilithium -level 2 -out mldsakey_priv -outform der -output priv -RESULT=$? -[ $RESULT -ne 0 ] && printf '%s\n' "Failed dilithium genkey -output PRIV" && exit 99 -[ ! -f mldsakey_priv.priv ] && printf '%s\n' "dilithium -output PRIV: .priv file missing" && exit 99 -[ -f mldsakey_priv.pub ] && printf '%s\n' "dilithium -output PRIV: .pub unexpectedly created" && exit 99 - -# Dilithium sign to an unwritable path must fail gracefully -./wolfssl -genkey dilithium -level 2 -out mldsakey -outform der -output keypair -./wolfssl -dilithium -sign -inkey mldsakey.priv -inform der \ - -in sign-this.txt -out /nonexistent_dir/mldsa_bad.sig -RESULT=$? -[ $RESULT -eq 0 ] && \ - printf '%s\n' "dilithium sign to invalid path should have failed" && exit 99 -fi - -# Check if xmss is availabe -if ./wolfssl xmss -help 2>&1 | grep -A6 "Available keys with current configure" | grep xmss; then - printf "Testing XMSS sign/verify\n" - ALGORITHM="xmss" - SIGOUTNAME="xmss-signed.sig" - DERPEMRAW="raw" - HEIGHT=10 - - gen_key_sign_ver_test ${ALGORITHM} "XMSS-SHA2_${HEIGHT}_256" ${SIGOUTNAME} ${DERPEMRAW} ${HEIGHT} - - # Too long to run - # for HEIGHT in 10 16 20 - # do - # KEYFILENAME="XMSS-SHA2_${HEIGHT}_256" - # gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} ${HEIGHT} - # done - - # ALGORITHM="xmssmt" - # SIGOUTNAME="xmssmt-signed.sig" - # DERPEMRAW="raw" - # HEIGHT=20 - - # for LAYER in 2 4 - # do - # KEYFILENAME="XMSSMT-SHA2_${HEIGHT}-${LAYER}_256" - # gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} ${HEIGHT} ${LAYER} - # done - - # ALGORITHM="xmssmt" - # SIGOUTNAME="xmssmt-signed.sig" - # DERPEMRAW="raw" - # HEIGHT=40 - - # for LAYER in 2 4 8 - # do - # KEYFILENAME="XMSSMT-SHA2_${HEIGHT}-${LAYER}_256" - # gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} ${HEIGHT} ${LAYER} - # done - - # ALGORITHM="xmssmt" - # SIGOUTNAME="xmssmt-signed.sig" - # DERPEMRAW="raw" - # HEIGHT=60 - - # for LAYER in 3 6 12 - # do - # KEYFILENAME="XMSSMT-SHA2_${HEIGHT}-${LAYER}_256" - # gen_key_sign_ver_test ${ALGORITHM} ${KEYFILENAME} ${SIGOUTNAME} ${DERPEMRAW} ${HEIGHT} ${LAYER} - # done -fi - - -exit 0 diff --git a/tests/genkey_sign_ver/include.am b/tests/genkey_sign_ver/include.am index 0a7e1265..6d97ec18 100644 --- a/tests/genkey_sign_ver/include.am +++ b/tests/genkey_sign_ver/include.am @@ -2,5 +2,5 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/genkey_sign_ver/genkey-sign-ver-test.sh +dist_noinst_SCRIPTS+=tests/genkey_sign_ver/genkey-sign-ver-test.py diff --git a/tests/hash/hash-test.py b/tests/hash/hash-test.py new file mode 100644 index 00000000..9c63a025 --- /dev/null +++ b/tests/hash/hash-test.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Hash tests for wolfCLU.""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, run_wolfssl + +HASH_DIR = os.path.join(".", "tests", "hash") +CERT_FILE = os.path.join(CERTS_DIR, "ca-cert.pem") + + +def _read_expected(name): + path = os.path.join(HASH_DIR, name) + with open(path, "r") as f: + return f.read().strip() + + +class HashCommandTest(unittest.TestCase): + """Tests using the -hash subcommand.""" + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + def test_sha_base64enc(self): + r = run_wolfssl("-hash", "sha", "-in", CERT_FILE, "-base64enc") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), _read_expected("sha-expect.hex")) + + def test_sha256(self): + r = run_wolfssl("-hash", "sha256", "-in", CERT_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), _read_expected("sha256-expect.hex")) + + def test_sha384(self): + r = run_wolfssl("-hash", "sha384", "-in", CERT_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), _read_expected("sha384-expect.hex")) + + def test_sha512(self): + r = run_wolfssl("-hash", "sha512", "-in", CERT_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), _read_expected("sha512-expect.hex")) + + +class HashShortcutTest(unittest.TestCase): + """Tests using the shortcut subcommands (md5, sha256, etc.).""" + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + def test_md5(self): + r = run_wolfssl("md5", CERT_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), _read_expected("md5-expect.hex")) + + def test_sha256(self): + r = run_wolfssl("sha256", CERT_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), _read_expected("sha256-expect.hex")) + + def test_sha384(self): + r = run_wolfssl("sha384", CERT_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), _read_expected("sha384-expect.hex")) + + def test_sha512(self): + r = run_wolfssl("sha512", CERT_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), _read_expected("sha512-expect.hex")) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/hash/hash-test.sh b/tests/hash/hash-test.sh deleted file mode 100755 index 3756c0e5..00000000 --- a/tests/hash/hash-test.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -run_success() { - RESULT=`./wolfssl $1` - if [ $? != 0 ]; then - echo "Failed on test \"$1\"" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - if [ $? == 0 ]; then - echo "Failed on test \"$1\"" - exit 99 - fi -} - -run_success "-hash sha -in certs/ca-cert.pem -base64enc" -EXPECTED=`cat tests/hash/sha-expect.hex` -if [ "$RESULT" != "$EXPECTED" ] -then - echo "found unexpected output 1" - exit 99 -fi - -run_success "-hash sha256 -in certs/ca-cert.pem" -EXPECTED=`cat tests/hash/sha256-expect.hex` -if [ "$RESULT" != "$EXPECTED" ] -then - echo "found unexpected output 2" - exit 99 -fi - -run_success "-hash sha384 -in certs/ca-cert.pem" -EXPECTED=`cat tests/hash/sha384-expect.hex` -if [ "$RESULT" != "$EXPECTED" ] -then - echo "found unexpected output 3" - exit 99 -fi - -run_success "-hash sha512 -in certs/ca-cert.pem" -EXPECTED=`cat tests/hash/sha512-expect.hex` -if [ "$RESULT" != "$EXPECTED" ] -then - echo "found unexpected output" - exit 99 -fi - - -run_success "md5 certs/ca-cert.pem" -EXPECTED=`cat tests/hash/md5-expect.hex` -if [ "$RESULT" != "$EXPECTED" ] -then - echo "found unexpected output" - exit 99 -fi - -run_success "sha256 certs/ca-cert.pem" -EXPECTED=`cat tests/hash/sha256-expect.hex` -if [ "$RESULT" != "$EXPECTED" ] -then - echo "found unexpected output" - exit 99 -fi - -run_success "sha384 certs/ca-cert.pem" -EXPECTED=`cat tests/hash/sha384-expect.hex` -if [ "$RESULT" != "$EXPECTED" ] -then - echo "found unexpected output" - exit 99 -fi - -run_success "sha512 certs/ca-cert.pem" -EXPECTED=`cat tests/hash/sha512-expect.hex` -if [ "$RESULT" != "$EXPECTED" ] -then - echo "found unexpected output" - exit 99 -fi - - -echo "Done" -exit 0 diff --git a/tests/hash/include.am b/tests/hash/include.am index a9032b9d..ced070a4 100644 --- a/tests/hash/include.am +++ b/tests/hash/include.am @@ -2,4 +2,4 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/hash/hash-test.sh +dist_noinst_SCRIPTS+=tests/hash/hash-test.py diff --git a/tests/ocsp-scgi/include.am b/tests/ocsp-scgi/include.am index f1de9ecb..bd80aa3b 100644 --- a/tests/ocsp-scgi/include.am +++ b/tests/ocsp-scgi/include.am @@ -3,7 +3,5 @@ # All paths should be given relative to the root # SCGI tests are run as part of make check -# Tests will be skipped if nginx or openssl are not available -dist_noinst_SCRIPTS += tests/ocsp-scgi/ocsp-scgi-test.sh - -EXTRA_DIST += tests/ocsp-scgi/scgi_params +# Tests will be skipped if openssl is not available +dist_noinst_SCRIPTS += tests/ocsp-scgi/ocsp-scgi-test.py diff --git a/tests/ocsp-scgi/ocsp-scgi-test.py b/tests/ocsp-scgi/ocsp-scgi-test.py new file mode 100644 index 00000000..4646e354 --- /dev/null +++ b/tests/ocsp-scgi/ocsp-scgi-test.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 +"""OCSP SCGI integration tests for wolfCLU. + +Replaces nginx with a minimal Python HTTP-to-SCGI proxy, eliminating +the nginx dependency. The SCGI protocol is simple enough to implement +inline (netstring header + body). + +Test flow: + openssl ocsp (HTTP) -> Python proxy -> wolfssl ocsp -scgi (SCGI) +""" + +import http.server +import os +import shutil +import socket +import subprocess +import sys +import tempfile +import threading +import time +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR + +HAS_OPENSSL = shutil.which("openssl") is not None + +SCGI_PORT = 6961 +HTTP_PORT = 8089 + +INDEX_VALID = ( + "V\t991231235959Z\t\t01\tunknown\t" + "/C=US/ST=Montana/L=Bozeman/O=wolfSSL/OU=Support" + "/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" +) +INDEX_REVOKED = ( + "R\t991231235959Z\t200101000000Z\t01\tunknown\t" + "/C=US/ST=Montana/L=Bozeman/O=wolfSSL/OU=Support" + "/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" +) + + +def _scgi_request(host, port, body, path="/ocsp"): + """Send an SCGI request and return the raw response body.""" + headers = ( + "CONTENT_LENGTH\x00" + str(len(body)) + "\x00" + "SCGI\x001\x00" + "REQUEST_METHOD\x00POST\x00" + "REQUEST_URI\x00" + path + "\x00" + "CONTENT_TYPE\x00application/ocsp-request\x00" + ) + header_bytes = headers.encode("ascii") + # Netstring: :, + netstring = str(len(header_bytes)).encode() + b":" + header_bytes + b"," + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(10) + try: + sock.connect((host, port)) + sock.sendall(netstring + body) + # Read full response + chunks = [] + while True: + data = sock.recv(4096) + if not data: + break + chunks.append(data) + return b"".join(chunks) + finally: + sock.close() + + +class _SCGIProxyHandler(http.server.BaseHTTPRequestHandler): + """HTTP handler that proxies POST requests to an SCGI backend.""" + + scgi_host = "127.0.0.1" + scgi_port = SCGI_PORT + + def do_POST(self): + length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(length) if length > 0 else b"" + + try: + raw = _scgi_request(self.scgi_host, self.scgi_port, + body, self.path) + except Exception as e: + self.send_error(502, str(e)) + return + + # The SCGI response may include HTTP-style headers followed by + # \r\n\r\n then the body, or it may be raw body only. + if b"\r\n\r\n" in raw: + header_part, resp_body = raw.split(b"\r\n\r\n", 1) + else: + resp_body = raw + self.send_response(200) + self.send_header("Content-Type", "application/ocsp-response") + self.send_header("Content-Length", str(len(resp_body))) + self.end_headers() + self.wfile.write(resp_body) + + def log_message(self, format, *args): + pass # suppress request logging + + +class _HTTPProxy: + """Runs the HTTP-to-SCGI proxy in a background thread.""" + + def __init__(self, http_port, scgi_port): + _SCGIProxyHandler.scgi_port = scgi_port + self.server = http.server.HTTPServer( + ("127.0.0.1", http_port), _SCGIProxyHandler) + self.thread = threading.Thread(target=self.server.serve_forever, + daemon=True) + + def start(self): + self.thread.start() + + def stop(self): + self.server.shutdown() + self.thread.join(timeout=5) + + +@unittest.skipUnless(HAS_OPENSSL, "openssl not available") +class TestOCSPScgi(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + # Check OCSP support + r = subprocess.run([WOLFSSL_BIN, "ocsp", "-help"], + capture_output=True, timeout=5) + if r.returncode != 0: + raise unittest.SkipTest("OCSP not supported") + + cls._tmpdir = tempfile.mkdtemp() + cls._wolfclu_proc = None + cls._wolfclu_log = None + cls._proxy = _HTTPProxy(HTTP_PORT, SCGI_PORT) + cls._proxy.start() + + @classmethod + def tearDownClass(cls): + if cls._wolfclu_proc: + cls._wolfclu_proc.terminate() + try: + cls._wolfclu_proc.wait(timeout=5) + except subprocess.TimeoutExpired: + cls._wolfclu_proc.kill() + if cls._wolfclu_log: + cls._wolfclu_log.close() + if hasattr(cls, "_proxy"): + cls._proxy.stop() + if hasattr(cls, "_tmpdir") and os.path.isdir(cls._tmpdir): + shutil.rmtree(cls._tmpdir, ignore_errors=True) + + def _write_index(self, content): + path = os.path.join(self._tmpdir, "index.txt") + with open(path, "w") as f: + f.write(content) + return path + + def _start_responder(self, index_content, + rsigner=None, rkey=None): + """Start wolfssl OCSP SCGI responder.""" + if self._wolfclu_proc and self._wolfclu_proc.poll() is None: + self._wolfclu_proc.terminate() + self._wolfclu_proc.wait(timeout=5) + if self._wolfclu_log: + self._wolfclu_log.close() + + index = self._write_index(index_content) + if rsigner is None: + rsigner = os.path.join(CERTS_DIR, "ca-cert.pem") + if rkey is None: + rkey = os.path.join(CERTS_DIR, "ca-key.pem") + + log_path = os.path.join(self._tmpdir, "scgi.log") + log_file = open(log_path, "w") + proc = subprocess.Popen( + [WOLFSSL_BIN, "ocsp", "-scgi", + "-port", str(SCGI_PORT), + "-index", index, + "-rsigner", rsigner, + "-rkey", rkey, + "-CA", os.path.join(CERTS_DIR, "ca-cert.pem")], + stdout=log_file, stderr=subprocess.STDOUT, + stdin=subprocess.DEVNULL, + ) + time.sleep(0.5) + if proc.poll() is not None: + log_file.close() + with open(log_path) as f: + self.fail(f"SCGI responder exited early: {f.read()}") + self.__class__._wolfclu_proc = proc + self.__class__._wolfclu_log = log_file + self._log_path = log_path + + def _ocsp_query(self): + """Run openssl ocsp via the HTTP proxy.""" + r = subprocess.run( + ["openssl", "ocsp", + "-issuer", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-cert", os.path.join(CERTS_DIR, "server-cert.pem"), + "-CAfile", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-url", f"http://127.0.0.1:{HTTP_PORT}/ocsp"], + capture_output=True, text=True, + stdin=subprocess.DEVNULL, timeout=30, + ) + return r.returncode, r.stdout + r.stderr + + def test_01_valid_cert(self): + """Valid certificate should return good status.""" + self._start_responder(INDEX_VALID) + rc, out = self._ocsp_query() + self.assertEqual(rc, 0, out) + self.assertIn("good", out.lower()) + + def test_02_revoked_cert(self): + """Revoked certificate should return revoked status.""" + self._start_responder(INDEX_REVOKED) + rc, out = self._ocsp_query() + self.assertIn("revoked", out.lower()) + + def test_03_valid_after_revoked(self): + """Valid cert after revoked index (stateless).""" + self._start_responder(INDEX_VALID) + rc, out = self._ocsp_query() + self.assertEqual(rc, 0, out) + self.assertIn("good", out.lower()) + + def test_04_multiple_requests(self): + """Multiple sequential requests should all succeed.""" + self._start_responder(INDEX_VALID) + for i in range(3): + with self.subTest(request=i + 1): + rc, out = self._ocsp_query() + self.assertEqual(rc, 0, f"request {i+1} failed: {out}") + + def test_05_delegated_responder(self): + """Valid cert with authorized/delegated responder.""" + self._start_responder( + INDEX_VALID, + rsigner=os.path.join(CERTS_DIR, "ocsp-responder-cert.pem"), + rkey=os.path.join(CERTS_DIR, "ocsp-responder-key.pem")) + rc, out = self._ocsp_query() + self.assertEqual(rc, 0, out) + self.assertIn("good", out.lower()) + + def test_06_delegated_revoked(self): + """Revoked cert with authorized/delegated responder.""" + self._start_responder( + INDEX_REVOKED, + rsigner=os.path.join(CERTS_DIR, "ocsp-responder-cert.pem"), + rkey=os.path.join(CERTS_DIR, "ocsp-responder-key.pem")) + rc, out = self._ocsp_query() + self.assertIn("revoked", out.lower()) + + def test_07_delegated_multiple(self): + """Multiple requests with delegated responder.""" + self._start_responder( + INDEX_VALID, + rsigner=os.path.join(CERTS_DIR, "ocsp-responder-cert.pem"), + rkey=os.path.join(CERTS_DIR, "ocsp-responder-key.pem")) + for i in range(3): + with self.subTest(request=i + 1): + rc, out = self._ocsp_query() + self.assertEqual(rc, 0, f"request {i+1} failed: {out}") + + @unittest.skipIf(sys.platform == "win32", + "TerminateProcess on Windows prevents graceful shutdown") + def test_08_graceful_shutdown(self): + """Responder should log graceful exit.""" + self._start_responder(INDEX_VALID) + self._ocsp_query() # at least one request + + self._wolfclu_proc.terminate() + self._wolfclu_proc.wait(timeout=5) + self._wolfclu_log.close() + self.__class__._wolfclu_proc = None + self.__class__._wolfclu_log = None + + with open(self._log_path) as f: + log = f.read() + self.assertIn("wolfssl exiting gracefully", log) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ocsp-scgi/ocsp-scgi-test.sh b/tests/ocsp-scgi/ocsp-scgi-test.sh deleted file mode 100755 index 99dc5746..00000000 --- a/tests/ocsp-scgi/ocsp-scgi-test.sh +++ /dev/null @@ -1,485 +0,0 @@ -#!/bin/bash - -# OCSP SCGI Integration Tests -# Tests wolfCLU OCSP SCGI mode with nginx proxying -# -# Usage: ocsp-scgi-test.sh [--keep-temp] -# -# Options: -# --keep-temp Don't delete temporary directory on exit (useful for debugging) -# -# Exit codes: -# 0 - All tests passed -# 77 - Tests skipped (missing dependencies) -# 99 - Tests failed - -set -e - -EXIT_SUCCESS=0 -EXIT_FAILURE=99 -EXIT_SKIP=77 - -# Track if tests failed (used in cleanup to print logs) -TESTS_FAILED=0 - -# Parse command line options -KEEP_TEMP=0 -if [ "$1" = "--keep-temp" ]; then - KEEP_TEMP=1 -fi - -# Get script directory -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -WOLFCLU_BIN="$REPO_ROOT/wolfssl" -CERTS_DIR="$REPO_ROOT/certs" - -if ! $WOLFCLU_BIN ocsp -help &> /dev/null; then - echo "ocsp not supported, skipping test" - exit 77 -fi - -# Create temporary directory for test files -TEMP_DIR=$(mktemp -d -t wolfclu-ocsp-scgi-XXXXXX) -TEST_DIR="$TEMP_DIR" -LOG_DIR="$TEMP_DIR/logs" - -# Cleanup function -cleanup() { - echo "Cleaning up..." - - # Kill wolfCLU SCGI responder - if [ -n "$WOLFCLU_PID" ] && kill -0 "$WOLFCLU_PID" 2>/dev/null; then - echo "Stopping wolfCLU SCGI responder (PID: $WOLFCLU_PID)..." - kill "$WOLFCLU_PID" 2>/dev/null || true - wait "$WOLFCLU_PID" 2>/dev/null || true - fi - - # Stop nginx - if [ -n "$NGINX_PID" ] && kill -0 "$NGINX_PID" 2>/dev/null; then - echo "Stopping nginx (PID: $NGINX_PID)..." - kill "$NGINX_PID" 2>/dev/null || true - wait "$NGINX_PID" 2>/dev/null || true - fi - - # Print all logs if tests failed - if [ "$TESTS_FAILED" = "1" ] && [ -d "$TEMP_DIR" ]; then - echo "" - echo "======================================" - echo "Tests failed - dumping all logs:" - echo "======================================" - - # Find all .log files in temp directory - while IFS= read -r -d '' logfile; do - if [ -s "$logfile" ]; then # Only show non-empty log files - echo "" - echo "--- $logfile ---" - cat "$logfile" - fi - done < <(find "$TEMP_DIR" -type f -name "*.log" -print0 2>/dev/null) - - echo "" - echo "======================================" - fi - - # Clean up temporary directory - if [ "$KEEP_TEMP" = "1" ]; then - echo "" - echo "======================================" - echo "Temporary directory preserved:" - echo "$TEMP_DIR" - echo "======================================" - echo "Contents:" - ls -lh "$TEMP_DIR" - if [ -d "$LOG_DIR" ]; then - echo "" - echo "Logs:" - ls -lh "$LOG_DIR" - fi - else - rm -rf "$TEMP_DIR" - fi -} - -trap cleanup EXIT INT TERM - -# Check prerequisites -echo "======================================" -echo "OCSP SCGI Integration Tests" -echo "======================================" - -# Check for nginx -if ! command -v nginx &> /dev/null; then - echo "nginx not found - skipping OCSP SCGI tests" - echo "Install nginx to run these tests: sudo apt-get install nginx" - exit $EXIT_SKIP -fi - -# Check for openssl -if ! command -v openssl &> /dev/null; then - echo "openssl not found - skipping OCSP SCGI tests" - exit $EXIT_SKIP -fi - -# Check for wolfCLU binary -if [ ! -x "$WOLFCLU_BIN" ]; then - echo "wolfCLU binary not found or not executable: $WOLFCLU_BIN" - echo "Build wolfCLU first: make" - exit $EXIT_SKIP -fi - -# Check for certificates -if [ ! -d "$CERTS_DIR" ]; then - echo "Certificates directory not found: $CERTS_DIR" - exit $EXIT_SKIP -fi - -echo "Prerequisites check passed" -echo "wolfCLU: $WOLFCLU_BIN" -echo "Certificates: $CERTS_DIR" -echo "Temp directory: $TEMP_DIR" -echo "Logs: $LOG_DIR" -if [ "$KEEP_TEMP" = "1" ]; then - echo "Keep temp: YES (will preserve on exit)" -fi -echo "" - -# Create log directory -mkdir -p "$LOG_DIR" - -# Create nginx temporary directories -mkdir -p "$TEMP_DIR/nginx_client_body" -mkdir -p "$TEMP_DIR/nginx_proxy" -mkdir -p "$TEMP_DIR/nginx_fastcgi" -mkdir -p "$TEMP_DIR/nginx_uwsgi" -mkdir -p "$TEMP_DIR/nginx_scgi" - -# Generate nginx configuration with proper temp directory paths -cat > "$TEMP_DIR/nginx.conf" < "$TEST_DIR/index.txt" - ;; - "revoked") - printf "R\t991231235959Z\t200101000000Z\t01\tunknown\t/C=US/ST=Montana/L=Bozeman/O=wolfSSL/OU=Support/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" > "$TEST_DIR/index.txt" - ;; - "empty") - touch "$TEST_DIR/index.txt" - ;; - *) - echo "Unknown mode: $mode" - return 1 - ;; - esac - - echo "unique_subject = no" > "$TEST_DIR/index.txt.attr" -} - -# Test helper function -# Usage: run_test [rsigner] [rkey] -run_test() { - local test_name="$1" - local index_setup="$2" - local expected_status="$3" - local rsigner="${4:-$CERTS_DIR/ca-cert.pem}" - local rkey="${5:-$CERTS_DIR/ca-key.pem}" - - echo "" - echo "======================================" - echo "Test: $test_name" - echo "======================================" - - # Setup index file - echo "Setting up index file..." - setup_index "$index_setup" - - # Restart wolfCLU SCGI responder with new index - if [ -n "$WOLFCLU_PID" ] && kill -0 "$WOLFCLU_PID" 2>/dev/null; then - echo "Restarting wolfCLU SCGI responder..." - kill "$WOLFCLU_PID" - wait "$WOLFCLU_PID" 2>/dev/null || true - fi - - # Start wolfCLU SCGI responder - echo "Starting wolfCLU SCGI responder (rsigner: $(basename "$rsigner"))..." - "$WOLFCLU_BIN" ocsp -scgi \ - -port 6961 \ - -index "$TEST_DIR/index.txt" \ - -rsigner "$rsigner" \ - -rkey "$rkey" \ - -CA "$CERTS_DIR/ca-cert.pem" \ - > "$LOG_DIR/wolfclu-scgi.log" 2>&1 & - WOLFCLU_PID=$! - - # Wait for responder to start - sleep 0.5 - - if ! kill -0 "$WOLFCLU_PID" 2>/dev/null; then - echo "ERROR: wolfCLU SCGI responder failed to start" - cat "$LOG_DIR/wolfclu-scgi.log" - return $EXIT_FAILURE - fi - - echo "wolfCLU SCGI responder started (PID: $WOLFCLU_PID)" - - # Make OCSP request via nginx - echo "Making OCSP request..." - - # Send OCSP request via HTTP to nginx (which forwards via SCGI to wolfCLU) - # openssl ocsp handles the entire HTTP transaction - OCSP_OUTPUT=$(openssl ocsp \ - -issuer "$CERTS_DIR/ca-cert.pem" \ - -cert "$CERTS_DIR/server-cert.pem" \ - -CAfile "$CERTS_DIR/ca-cert.pem" \ - -url http://localhost:8080/ocsp 2>&1) - - echo "$OCSP_OUTPUT" - - if echo "$OCSP_OUTPUT" | grep -q "$expected_status"; then - echo "✓ Test PASSED: Found expected status '$expected_status'" - return $EXIT_SUCCESS - else - echo "✗ Test FAILED: Expected '$expected_status' but got different status" - return $EXIT_FAILURE - fi -} - -# Start nginx -echo "Starting nginx..." - -nginx -c "$TEMP_DIR/nginx.conf" > "$LOG_DIR/nginx-startup.log" 2>&1 & -NGINX_PID=$! -sleep 0.5 - -if ! kill -0 "$NGINX_PID" 2>/dev/null; then - echo "ERROR: nginx failed to start" - cat "$LOG_DIR/nginx-startup.log" - exit $EXIT_FAILURE -fi - -echo "nginx started (PID: $NGINX_PID)" - -# Run tests -FAILED_TESTS=0 -PASSED_TESTS=0 - -# Test 1: Valid certificate -if run_test "Valid certificate (good status)" "valid" "good"; then - PASSED_TESTS=$((PASSED_TESTS + 1)) -else - FAILED_TESTS=$((FAILED_TESTS + 1)) -fi - -# Test 2: Revoked certificate -if run_test "Revoked certificate" "revoked" "revoked"; then - PASSED_TESTS=$((PASSED_TESTS + 1)) -else - FAILED_TESTS=$((FAILED_TESTS + 1)) -fi - -# Test 3: Valid certificate after revoked (stateless verification) -if run_test "Valid certificate again (stateless)" "valid" "good"; then - PASSED_TESTS=$((PASSED_TESTS + 1)) -else - FAILED_TESTS=$((FAILED_TESTS + 1)) -fi - -# Test 4: Multiple requests to same responder -echo "" -echo "======================================" -echo "Test: Multiple sequential requests" -echo "======================================" - -MULTI_REQUEST_SUCCESS=1 -for i in 1 2 3; do - echo "Request $i of 3..." - - # Send OCSP request via openssl (handles HTTP internally) - if openssl ocsp \ - -issuer "$CERTS_DIR/ca-cert.pem" \ - -cert "$CERTS_DIR/server-cert.pem" \ - -CAfile "$CERTS_DIR/ca-cert.pem" \ - -url http://localhost:8080/ocsp > /dev/null 2>&1; then - echo "✓ Request $i successful" - else - echo "✗ Request $i failed" - MULTI_REQUEST_SUCCESS=0 - break - fi -done - -if [ "$MULTI_REQUEST_SUCCESS" = "1" ]; then - echo "✓ Test PASSED: All 3 requests successful" - PASSED_TESTS=$((PASSED_TESTS + 1)) -else - echo "✗ Test FAILED: Not all requests successful" - FAILED_TESTS=$((FAILED_TESTS + 1)) -fi - -# --- Tests with authorized/delegated OCSP responder --- - -# Test 5: Valid certificate with authorized responder -if run_test "Valid certificate with authorized responder" "valid" "good" \ - "$CERTS_DIR/ocsp-responder-cert.pem" "$CERTS_DIR/ocsp-responder-key.pem"; then - PASSED_TESTS=$((PASSED_TESTS + 1)) -else - FAILED_TESTS=$((FAILED_TESTS + 1)) -fi - -# Test 6: Revoked certificate with authorized responder -if run_test "Revoked certificate with authorized responder" "revoked" "revoked" \ - "$CERTS_DIR/ocsp-responder-cert.pem" "$CERTS_DIR/ocsp-responder-key.pem"; then - PASSED_TESTS=$((PASSED_TESTS + 1)) -else - FAILED_TESTS=$((FAILED_TESTS + 1)) -fi - -# Test 7: Valid certificate after revoked with authorized responder (stateless) -if run_test "Valid certificate again with authorized responder (stateless)" "valid" "good" \ - "$CERTS_DIR/ocsp-responder-cert.pem" "$CERTS_DIR/ocsp-responder-key.pem"; then - PASSED_TESTS=$((PASSED_TESTS + 1)) -else - FAILED_TESTS=$((FAILED_TESTS + 1)) -fi - -# Test 8: Multiple requests with authorized responder -echo "" -echo "======================================" -echo "Test: Multiple sequential requests with authorized responder" -echo "======================================" - -setup_index "valid" - -# Restart with authorized responder -if [ -n "$WOLFCLU_PID" ] && kill -0 "$WOLFCLU_PID" 2>/dev/null; then - kill "$WOLFCLU_PID" - wait "$WOLFCLU_PID" 2>/dev/null || true -fi - -"$WOLFCLU_BIN" ocsp -scgi \ - -port 6961 \ - -index "$TEST_DIR/index.txt" \ - -rsigner "$CERTS_DIR/ocsp-responder-cert.pem" \ - -rkey "$CERTS_DIR/ocsp-responder-key.pem" \ - -CA "$CERTS_DIR/ca-cert.pem" \ - > "$LOG_DIR/wolfclu-scgi.log" 2>&1 & -WOLFCLU_PID=$! -sleep 0.5 - -if ! kill -0 "$WOLFCLU_PID" 2>/dev/null; then - echo "ERROR: wolfCLU SCGI responder failed to start" - cat "$LOG_DIR/wolfclu-scgi.log" - FAILED_TESTS=$((FAILED_TESTS + 1)) -else - MULTI_REQUEST_SUCCESS=1 - for i in 1 2 3; do - echo "Request $i of 3..." - if openssl ocsp \ - -issuer "$CERTS_DIR/ca-cert.pem" \ - -cert "$CERTS_DIR/server-cert.pem" \ - -CAfile "$CERTS_DIR/ca-cert.pem" \ - -url http://localhost:8080/ocsp > /dev/null 2>&1; then - echo "✓ Request $i successful" - else - echo "✗ Request $i failed" - MULTI_REQUEST_SUCCESS=0 - break - fi - done - - if [ "$MULTI_REQUEST_SUCCESS" = "1" ]; then - echo "✓ Test PASSED: All 3 requests successful" - PASSED_TESTS=$((PASSED_TESTS + 1)) - else - echo "✗ Test FAILED: Not all requests successful" - FAILED_TESTS=$((FAILED_TESTS + 1)) - fi -fi - -# Stop the last responder and verify graceful shutdown -echo "" -echo "======================================" -echo "Verifying graceful shutdown" -echo "======================================" - -if [ -n "$WOLFCLU_PID" ] && kill -0 "$WOLFCLU_PID" 2>/dev/null; then - echo "Stopping wolfCLU SCGI responder..." - kill "$WOLFCLU_PID" 2>/dev/null || true - wait "$WOLFCLU_PID" 2>/dev/null || true - WOLFCLU_PID="" -fi - -# Check for graceful exit message in logs -if [ -f "$LOG_DIR/wolfclu-scgi.log" ]; then - if grep -q "wolfssl exiting gracefully" "$LOG_DIR/wolfclu-scgi.log"; then - echo "✓ Found graceful exit message in wolfclu-scgi.log" - else - echo "✗ ERROR: No 'wolfssl exiting gracefully' message found in wolfclu-scgi.log" - echo " The responder did not shut down gracefully" - FAILED_TESTS=$((FAILED_TESTS + 1)) - fi -else - echo "✗ ERROR: wolfclu-scgi.log not found" - FAILED_TESTS=$((FAILED_TESTS + 1)) -fi - -# Summary -echo "" -echo "======================================" -echo "Test Summary" -echo "======================================" -echo "Passed: $PASSED_TESTS" -echo "Failed: $FAILED_TESTS" -echo "======================================" - -if [ "$FAILED_TESTS" -gt 0 ]; then - TESTS_FAILED=1 - echo "Some tests failed. Check logs above." - exit $EXIT_FAILURE -else - echo "All tests passed!" - exit $EXIT_SUCCESS -fi diff --git a/tests/ocsp-scgi/scgi_params b/tests/ocsp-scgi/scgi_params deleted file mode 100644 index c8c725c1..00000000 --- a/tests/ocsp-scgi/scgi_params +++ /dev/null @@ -1,16 +0,0 @@ -scgi_param REQUEST_METHOD $request_method; -scgi_param REQUEST_URI $request_uri; -scgi_param QUERY_STRING $query_string; -scgi_param CONTENT_TYPE $content_type; -scgi_param CONTENT_LENGTH $content_length; - -scgi_param SCRIPT_NAME $fastcgi_script_name; -scgi_param DOCUMENT_URI $document_uri; -scgi_param DOCUMENT_ROOT $document_root; -scgi_param SERVER_PROTOCOL $server_protocol; -scgi_param HTTPS $https if_not_empty; - -scgi_param REMOTE_ADDR $remote_addr; -scgi_param REMOTE_PORT $remote_port; -scgi_param SERVER_PORT $server_port; -scgi_param SERVER_NAME $server_name; diff --git a/tests/ocsp/include.am b/tests/ocsp/include.am index f6da6716..8c046f61 100644 --- a/tests/ocsp/include.am +++ b/tests/ocsp/include.am @@ -3,4 +3,4 @@ # All paths should be given relative to root directory # Only register the consolidated test - interop test is a helper -dist_noinst_SCRIPTS+=tests/ocsp/ocsp-test.sh +dist_noinst_SCRIPTS+=tests/ocsp/ocsp-test.py diff --git a/tests/ocsp/ocsp-interop-test.sh b/tests/ocsp/ocsp-interop-test.sh deleted file mode 100755 index 49c8a75a..00000000 --- a/tests/ocsp/ocsp-interop-test.sh +++ /dev/null @@ -1,520 +0,0 @@ -#!/bin/bash - -# Generic OCSP interoperability test -# Uses environment variables to determine which binaries to use: -# OCSP_CLIENT - binary to use for OCSP client (wolfclu or openssl) -# OCSP_RESPONDER - binary to use for OCSP responder (wolfclu or openssl) -# KEEP_TEST_DIR - if set to 1, preserve test directory for debugging -# -# Test coverage: -# - Positive tests: Valid certificate checks with various options -# - Negative tests: Revoked certificates, missing parameters, invalid files -# - Return code compatibility: Verifies wolfssl ocsp is compatible with openssl ocsp -# -# Exit codes: -# 0 - All tests passed -# 77 - Test skipped (filesystem disabled, OCSP not supported, etc.) -# 99 - Test failed - -if [ ! -d ./certs/ ]; then - echo "certs directory not found, skipping test" - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ]; then - echo "Filesystem disabled, skipping test" - exit 77 -fi - -# Determine client and responder binaries -if [ -z "$OCSP_CLIENT" ]; then - echo "Client not specified" - exit 99 -fi - -if [ -z "$OCSP_RESPONDER" ]; then - echo "Responder not specified" - exit 99 -fi - -echo "Testing OCSP interop: $OCSP_CLIENT (client) vs $OCSP_RESPONDER (responder)" - -if ! $OCSP_CLIENT ocsp -help &> /dev/null; then - echo "ocsp not supported on client side, skipping test" - exit 77 -fi -if ! $OCSP_RESPONDER ocsp -help &> /dev/null; then - echo "ocsp not supported on responder side, skipping test" - exit 77 -fi - -# Create a temporary directory for test files -TEST_DIR=$(mktemp -d) -if [ $? != 0 ]; then - echo "Failed to create temp directory" - exit 99 -fi - -cleanup() { - EXIT_CODE=$? - - # Print logs on error - if [ $EXIT_CODE != 0 ] && [ $EXIT_CODE != 77 ]; then - echo "====================================" - echo "Test failed with exit code: $EXIT_CODE" - echo "====================================" - - for logfile in "$TEST_DIR"/*.log; do - if [ -f "$logfile" ]; then - echo "$(basename "$logfile"):" - cat "$logfile" - echo "------------------------------------" - fi - done - fi - - # Kill the OCSP responder if still running - if [ ! -z "$RESPONDER_PID" ]; then - kill $RESPONDER_PID 2>/dev/null - wait $RESPONDER_PID 2>/dev/null - fi - - # Remove test directory unless KEEP_TEST_DIR is set - if [ "$KEEP_TEST_DIR" = "1" ]; then - echo "Test directory preserved: $TEST_DIR" - else - rm -rf "$TEST_DIR" - fi -} - -trap cleanup EXIT - -# Create an OCSP index file for the test -# Format: statusexpirationrevocationserialfilenameDN -# V = valid, R = revoked, E = expired -# Use printf to ensure proper tab separators -printf "V\t991231235959Z\t\t01\tunknown\t/C=US/ST=Montana/L=Bozeman/O=wolfSSL/OU=Support/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" > "$TEST_DIR/index.txt" - -OCSP_PORT=6960 - -# Start OCSP responder in background -$OCSP_RESPONDER ocsp -port $OCSP_PORT \ - -index "$TEST_DIR/index.txt" \ - -CA certs/ca-cert.pem \ - -rsigner certs/ca-cert.pem \ - -rkey certs/ca-key.pem \ - -nrequest 10 \ - > "$TEST_DIR/ocsp-responder.log" 2>&1 & -RESPONDER_PID=$! - -# Wait for responder to start -sleep 0.5 - -# Check if responder is still running -if ! kill -0 $RESPONDER_PID 2>/dev/null; then - echo "OCSP responder failed to start" - exit 99 -fi - -echo "OCSP responder started on port $OCSP_PORT (PID: $RESPONDER_PID)" - -# Run client tests -# Test 1: Basic OCSP check with CA file and explicit URL -echo "Test 1: OCSP check with -CAfile and -url" - -$OCSP_CLIENT ocsp \ - -issuer certs/ca-cert.pem \ - -cert certs/server-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - > "$TEST_DIR/test1.log" 2>&1 - -RESULT=$? -if [ $RESULT != 0 ]; then - echo "Test 1 failed: $OCSP_CLIENT OCSP check returned $RESULT" - exit 99 -fi - -# Verify the output contains success indicator -grep -q "good" "$TEST_DIR/test1.log" -if [ $? != 0 ]; then - echo "Test 1 failed: expected success indicator in output" - exit 99 -fi - -echo "Test 1 passed" - -# Test 2: OCSP check with -no_nonce -echo "Test 2: OCSP check with -no_nonce" - -$OCSP_CLIENT ocsp \ - -issuer certs/ca-cert.pem \ - -cert certs/server-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - -no_nonce \ - > "$TEST_DIR/test2.log" 2>&1 - -RESULT=$? -if [ $RESULT != 0 ]; then - echo "Test 2 failed: $OCSP_CLIENT OCSP check with -no_nonce returned $RESULT" - exit 99 -fi - -grep -q "good" "$TEST_DIR/test2.log" -if [ $? != 0 ]; then - echo "Test 2 failed: expected success indicator in output" - exit 99 -fi - -echo "Test 2 passed" - -# Test 3: OCSP check for revoked certificate -echo "Test 3: OCSP check for revoked certificate (should show revoked status)" - -# Note: OpenSSL OCSP returns exit code 0 even for revoked certificates, because -# the OCSP transaction itself succeeded. The revocation status is in the output. -# wolfssl OCSP responder currently has a limitation generating revoked responses. - - -# Update index.txt to include the revoked certificate (serial 02) -printf "V\t991231235959Z\t\t01\tunknown\t/C=US/ST=Montana/L=Bozeman/O=wolfSSL/OU=Support/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" > "$TEST_DIR/index.txt" -printf "R\t991231235959Z\t240101000000Z\t02\tunknown\t/C=US/ST=Montana/L=Bozeman/O=wolfSSL_revoked/OU=Support_revoked/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" >> "$TEST_DIR/index.txt" - -# Restart responder with new index -if [ ! -z "$RESPONDER_PID" ]; then - kill $RESPONDER_PID 2>/dev/null - wait $RESPONDER_PID 2>/dev/null -fi - -$OCSP_RESPONDER ocsp -port $OCSP_PORT \ - -index "$TEST_DIR/index.txt" \ - -CA certs/ca-cert.pem \ - -rsigner certs/ca-cert.pem \ - -rkey certs/ca-key.pem \ - -nrequest 10 \ - > "$TEST_DIR/ocsp-responder2.log" 2>&1 & -RESPONDER_PID=$! - -sleep 0.5 - -if ! kill -0 $RESPONDER_PID 2>/dev/null; then - echo "Test 3 failed: OCSP responder failed to restart" - exit 99 -fi - -# Check the revoked certificate -$OCSP_CLIENT ocsp \ - -issuer certs/ca-cert.pem \ - -cert certs/server-revoked-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - > "$TEST_DIR/test3.log" 2>&1 - -RESULT=$? - -# OpenSSL returns 0 (success) even for revoked certs - the status is in output -# Check the output for revoked status indicator -if grep -qi "revoked" "$TEST_DIR/test3.log"; then - # Found revoked status - this is correct - echo "Test 3 passed" -else - # Didn't find any revoked indicator - echo "Test 3 failed: expected revoked status indicator in output" - cat "$TEST_DIR/test3.log" - exit 99 -fi - -# Test 4: Missing required parameter (-cert without -issuer) -echo "Test 4: Missing required parameter (no issuer)" - -$OCSP_CLIENT ocsp \ - -cert certs/server-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - > "$TEST_DIR/test4.log" 2>&1 - -RESULT=$? -if [ $RESULT = 0 ]; then - echo "Test 4 failed: $OCSP_CLIENT should have failed without -issuer" - exit 99 -fi - -# Check for error message about missing issuer -grep -qi "issuer" "$TEST_DIR/test4.log" -if [ $? != 0 ]; then - echo "Test 4 failed: expected error about missing issuer" - exit 99 -fi - -echo "Test 4 passed" - -# Test 5: Missing required parameter (-issuer without -cert) -echo "Test 5: Missing required parameter (no cert)" - -$OCSP_CLIENT ocsp \ - -issuer certs/ca-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - > "$TEST_DIR/test5.log" 2>&1 - -RESULT=$? -if [ $RESULT = 0 ]; then - echo "Test 5 failed: $OCSP_CLIENT should have failed without -cert" - exit 99 -fi - -# Check for error message about missing cert or help output -# OpenSSL shows help usage, wolfssl shows an error -grep -qi "cert\|help\|usage" "$TEST_DIR/test5.log" -if [ $? != 0 ]; then - echo "Test 5 failed: expected error about missing cert or help output" - exit 99 -fi - -echo "Test 5 passed" - -# Test 6: Invalid certificate file -echo "Test 6: Invalid certificate file" - -$OCSP_CLIENT ocsp \ - -issuer certs/ca-cert.pem \ - -cert /nonexistent/file.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - > "$TEST_DIR/test6.log" 2>&1 - -RESULT=$? -if [ $RESULT = 0 ]; then - echo "Test 6 failed: $OCSP_CLIENT should have failed with invalid cert file" - exit 99 -fi - -# Check for error message -grep -qi "fail\|error\|not found\|unable" "$TEST_DIR/test6.log" -if [ $? != 0 ]; then - echo "Test 6 failed: expected error message about invalid file" - exit 99 -fi - -echo "Test 6 passed" - -# Test 7: Invalid issuer certificate file -echo "Test 7: Invalid issuer certificate file" - -$OCSP_CLIENT ocsp \ - -issuer /nonexistent/issuer.pem \ - -cert certs/server-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - > "$TEST_DIR/test7.log" 2>&1 - -RESULT=$? -if [ $RESULT = 0 ]; then - echo "Test 7 failed: $OCSP_CLIENT should have failed with invalid issuer file" - exit 99 -fi - -# Check for error message -grep -qi "fail\|error\|unable\|issuer" "$TEST_DIR/test7.log" -if [ $? != 0 ]; then - echo "Test 7 failed: expected error message about invalid issuer file" - exit 99 -fi - -echo "Test 7 passed" - -# --- Tests with delegated OCSP responder (ocsp-responder-cert.pem as -rsigner) --- - -# Kill current responder and restart with delegated responder cert -if [ ! -z "$RESPONDER_PID" ]; then - kill $RESPONDER_PID 2>/dev/null - wait $RESPONDER_PID 2>/dev/null - RESPONDER_PID="" -fi - -# Reset index to valid-only for delegated responder tests -printf "V\t991231235959Z\t\t01\tunknown\t/C=US/ST=Montana/L=Bozeman/O=wolfSSL/OU=Support/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" > "$TEST_DIR/index.txt" - -$OCSP_RESPONDER ocsp -port $OCSP_PORT \ - -index "$TEST_DIR/index.txt" \ - -CA certs/ca-cert.pem \ - -rsigner certs/ocsp-responder-cert.pem \ - -rkey certs/ocsp-responder-key.pem \ - -nrequest 10 \ - > "$TEST_DIR/ocsp-responder-deleg.log" 2>&1 & -RESPONDER_PID=$! - -sleep 0.5 - -if ! kill -0 $RESPONDER_PID 2>/dev/null; then - echo "Delegated OCSP responder failed to start" - exit 99 -fi - -echo "Delegated OCSP responder started on port $OCSP_PORT (PID: $RESPONDER_PID)" - -# Test 8: Basic OCSP check with delegated responder -echo "Test 8: OCSP check with delegated responder (-rsigner ocsp-responder-cert.pem)" - -$OCSP_CLIENT ocsp \ - -issuer certs/ca-cert.pem \ - -cert certs/server-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - > "$TEST_DIR/test8.log" 2>&1 - -RESULT=$? -if [ $RESULT != 0 ]; then - echo "Test 8 failed: $OCSP_CLIENT OCSP check with delegated responder returned $RESULT" - exit 99 -fi - -grep -q "good" "$TEST_DIR/test8.log" -if [ $? != 0 ]; then - echo "Test 8 failed: expected success indicator in output" - exit 99 -fi - -echo "Test 8 passed" - -# Test 9: OCSP check with delegated responder and -no_nonce -echo "Test 9: OCSP check with delegated responder and -no_nonce" - -$OCSP_CLIENT ocsp \ - -issuer certs/ca-cert.pem \ - -cert certs/server-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - -no_nonce \ - > "$TEST_DIR/test9.log" 2>&1 - -RESULT=$? -if [ $RESULT != 0 ]; then - echo "Test 9 failed: $OCSP_CLIENT OCSP check with delegated responder and -no_nonce returned $RESULT" - exit 99 -fi - -grep -q "good" "$TEST_DIR/test9.log" -if [ $? != 0 ]; then - echo "Test 9 failed: expected success indicator in output" - exit 99 -fi - -echo "Test 9 passed" - -# Test 10: Revoked cert check with delegated responder -echo "Test 10: OCSP revoked cert check with delegated responder" - -# Update index to include revoked certificate -printf "V\t991231235959Z\t\t01\tunknown\t/C=US/ST=Montana/L=Bozeman/O=wolfSSL/OU=Support/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" > "$TEST_DIR/index.txt" -printf "R\t991231235959Z\t240101000000Z\t02\tunknown\t/C=US/ST=Montana/L=Bozeman/O=wolfSSL_revoked/OU=Support_revoked/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" >> "$TEST_DIR/index.txt" - -# Restart delegated responder with updated index -if [ ! -z "$RESPONDER_PID" ]; then - kill $RESPONDER_PID 2>/dev/null - wait $RESPONDER_PID 2>/dev/null -fi - -$OCSP_RESPONDER ocsp -port $OCSP_PORT \ - -index "$TEST_DIR/index.txt" \ - -CA certs/ca-cert.pem \ - -rsigner certs/ocsp-responder-cert.pem \ - -rkey certs/ocsp-responder-key.pem \ - -nrequest 10 \ - > "$TEST_DIR/ocsp-responder-deleg2.log" 2>&1 & -RESPONDER_PID=$! - -sleep 0.5 - -if ! kill -0 $RESPONDER_PID 2>/dev/null; then - echo "Test 10 failed: delegated OCSP responder failed to restart" - exit 99 -fi - -$OCSP_CLIENT ocsp \ - -issuer certs/ca-cert.pem \ - -cert certs/server-revoked-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - > "$TEST_DIR/test10.log" 2>&1 - -if grep -qi "revoked" "$TEST_DIR/test10.log"; then - echo "Test 10 passed" -else - echo "Test 10 failed: expected revoked status indicator in output" - cat "$TEST_DIR/test10.log" - exit 99 -fi - -# Test 11: Unreachable OCSP responder -echo "Test 11: Unreachable OCSP responder" - -# Kill the responder temporarily -if [ ! -z "$RESPONDER_PID" ]; then - kill $RESPONDER_PID 2>/dev/null - wait $RESPONDER_PID 2>/dev/null - RESPONDER_PID="" -fi - -$OCSP_CLIENT ocsp \ - -issuer certs/ca-cert.pem \ - -cert certs/server-cert.pem \ - -CAfile certs/ca-cert.pem \ - -url http://127.0.0.1:$OCSP_PORT \ - > "$TEST_DIR/test11.log" 2>&1 - -RESULT=$? -if [ $RESULT = 0 ]; then - echo "Test 11 failed: $OCSP_CLIENT should have failed with unreachable responder" - exit 99 -fi - -# Check for connection/network error -grep -qi "fail\|error\|connect\|timeout\|refused" "$TEST_DIR/test11.log" -if [ $? != 0 ]; then - echo "Test 11 failed: expected connection error message" - exit 99 -fi - -echo "Test 11 passed" - -# Verify graceful exit messages in responder logs (for wolfCLU responders only) -if [ "$OCSP_RESPONDER" = "./wolfssl" ]; then - echo "" - echo "Verifying graceful shutdown messages..." - - # Check each responder log file for the graceful exit message - MISSING_LOGS="" - LOG_COUNT=0 - - for logfile in "$TEST_DIR"/ocsp-responder*.log; do - if [ -f "$logfile" ]; then - LOG_COUNT=$((LOG_COUNT + 1)) - if grep -q "wolfssl exiting gracefully" "$logfile"; then - echo "✓ Found graceful exit message in $(basename "$logfile")" - else - echo "✗ Missing graceful exit message in $(basename "$logfile")" - MISSING_LOGS="$MISSING_LOGS $(basename "$logfile")" - fi - fi - done - - if [ $LOG_COUNT -eq 0 ]; then - echo "ERROR: No responder log files found" - exit 99 - fi - - if [ -n "$MISSING_LOGS" ]; then - echo "" - echo "ERROR: The following responder logs are missing graceful exit messages:" - echo "$MISSING_LOGS" - echo "All responders must shut down gracefully" - exit 99 - fi -fi - -echo "All OCSP interop tests passed" -exit 0 diff --git a/tests/ocsp/ocsp-test.py b/tests/ocsp/ocsp-test.py new file mode 100644 index 00000000..6fe89077 --- /dev/null +++ b/tests/ocsp/ocsp-test.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 +"""OCSP interoperability tests for wolfCLU. + +Combines ocsp-test.sh and ocsp-interop-test.sh into a single Python +test module. Tests all client/responder combinations (wolfssl, openssl). +""" + +import os +import re +import shutil +import subprocess +import sys +import tempfile +import time +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR + +HAS_OPENSSL = shutil.which("openssl") is not None +OCSP_PORT_BASE = 6960 + +INDEX_VALID = ( + "V\t991231235959Z\t\t01\tunknown\t" + "/C=US/ST=Montana/L=Bozeman/O=wolfSSL/OU=Support" + "/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" +) + +INDEX_REVOKED = ( + "R\t991231235959Z\t240101000000Z\t02\tunknown\t" + "/C=US/ST=Montana/L=Bozeman/O=wolfSSL_revoked/OU=Support_revoked" + "/CN=www.wolfssl.com/emailAddress=info@wolfssl.com\n" +) + + +def _ocsp_supported(binary): + """Check if the given binary supports OCSP.""" + try: + r = subprocess.run([binary, "ocsp", "-help"], + capture_output=True, timeout=5) + return True + except (FileNotFoundError, subprocess.TimeoutExpired): + return False + + +class _OCSPResponder: + """Context manager that starts an OCSP responder and cleans up on exit.""" + + def __init__(self, binary, port, index_path, rsigner, rkey, nrequest=10): + self.cmd = [ + binary, "ocsp", "-port", str(port), + "-index", index_path, + "-CA", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-rsigner", rsigner, + "-rkey", rkey, + "-nrequest", str(nrequest), + ] + self.proc = None + self.log_path = None + + def start(self, log_path): + self.log_path = log_path + self.log_file = open(log_path, "w") + self.proc = subprocess.Popen( + self.cmd, + stdout=self.log_file, + stderr=subprocess.STDOUT, + stdin=subprocess.DEVNULL, + ) + # Wait for responder to bind + time.sleep(0.5) + if self.proc.poll() is not None: + self.log_file.close() + raise RuntimeError( + f"Responder exited early (rc={self.proc.returncode})") + + def stop(self): + if self.proc and self.proc.poll() is None: + self.proc.terminate() + try: + self.proc.wait(timeout=5) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait() + if hasattr(self, "log_file") and self.log_file: + self.log_file.close() + + def read_log(self): + if self.log_path and os.path.isfile(self.log_path): + with open(self.log_path, "r") as f: + return f.read() + return "" + + +def _run_client(binary, port, extra_args=None): + """Run an OCSP client query and return (returncode, combined output).""" + cmd = [ + binary, "ocsp", + "-issuer", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-CAfile", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-url", f"http://127.0.0.1:{port}", + ] + if extra_args: + cmd.extend(extra_args) + r = subprocess.run(cmd, capture_output=True, text=True, + stdin=subprocess.DEVNULL, timeout=30) + return r.returncode, r.stdout + r.stderr + + +class _OCSPInteropBase(unittest.TestCase): + """Base class for a single client/responder combination.""" + + CLIENT_BIN = None + RESPONDER_BIN = None + PORT = OCSP_PORT_BASE + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + if cls.CLIENT_BIN is None or cls.RESPONDER_BIN is None: + raise unittest.SkipTest("binary not configured") + if not _ocsp_supported(cls.CLIENT_BIN): + raise unittest.SkipTest(f"OCSP not supported by {cls.CLIENT_BIN}") + if not _ocsp_supported(cls.RESPONDER_BIN): + raise unittest.SkipTest( + f"OCSP not supported by {cls.RESPONDER_BIN}") + + cls._tmpdir = tempfile.mkdtemp() + cls._responder = None + + @classmethod + def tearDownClass(cls): + if cls._responder: + cls._responder.stop() + if hasattr(cls, "_tmpdir") and os.path.isdir(cls._tmpdir): + shutil.rmtree(cls._tmpdir, ignore_errors=True) + + def _write_index(self, entries): + path = os.path.join(self._tmpdir, "index.txt") + with open(path, "w") as f: + f.write(entries) + return path + + def _start_responder(self, index_text, rsigner=None, rkey=None, + nrequest=10): + """Stop existing responder (if any) and start a new one.""" + if self._responder: + self._responder.stop() + + index = self._write_index(index_text) + if rsigner is None: + rsigner = os.path.join(CERTS_DIR, "ca-cert.pem") + if rkey is None: + rkey = os.path.join(CERTS_DIR, "ca-key.pem") + + log = os.path.join(self._tmpdir, + f"responder-{time.monotonic_ns()}.log") + resp = _OCSPResponder(self.RESPONDER_BIN, self.PORT, index, + rsigner, rkey, nrequest) + resp.start(log) + self.__class__._responder = resp + return resp + + def _query(self, cert, extra_args=None): + args = ["-cert", os.path.join(CERTS_DIR, cert)] + if extra_args: + args.extend(extra_args) + return _run_client(self.CLIENT_BIN, self.PORT, args) + + # -- Positive tests -- + + def test_01_basic_check(self): + self._start_responder(INDEX_VALID) + rc, out = self._query("server-cert.pem") + self.assertEqual(rc, 0, f"basic OCSP check failed: {out}") + self.assertIn("good", out.lower()) + + def test_02_no_nonce(self): + self._start_responder(INDEX_VALID) + rc, out = self._query("server-cert.pem", ["-no_nonce"]) + self.assertEqual(rc, 0, f"no_nonce check failed: {out}") + self.assertIn("good", out.lower()) + + # -- Revoked cert -- + + def test_03_revoked_cert(self): + self._start_responder(INDEX_VALID + INDEX_REVOKED) + rc, out = self._query("server-revoked-cert.pem") + self.assertRegex(out, re.compile("revoked", re.IGNORECASE), + "expected 'revoked' in output") + + # -- Missing parameters -- + + def test_04_missing_issuer(self): + self._start_responder(INDEX_VALID) + cmd = [ + self.CLIENT_BIN, "ocsp", + "-cert", os.path.join(CERTS_DIR, "server-cert.pem"), + "-CAfile", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-url", f"http://127.0.0.1:{self.PORT}", + ] + r = subprocess.run(cmd, capture_output=True, text=True, + stdin=subprocess.DEVNULL, timeout=30) + self.assertNotEqual(r.returncode, 0) + self.assertRegex(r.stdout + r.stderr, + re.compile("issuer", re.IGNORECASE)) + + def test_05_missing_cert(self): + self._start_responder(INDEX_VALID) + cmd = [ + self.CLIENT_BIN, "ocsp", + "-issuer", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-CAfile", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-url", f"http://127.0.0.1:{self.PORT}", + ] + r = subprocess.run(cmd, capture_output=True, text=True, + stdin=subprocess.DEVNULL, timeout=30) + self.assertNotEqual(r.returncode, 0) + self.assertRegex(r.stdout + r.stderr, + re.compile("cert|help|usage", re.IGNORECASE)) + + # -- Invalid files -- + + def test_06_invalid_cert_file(self): + self._start_responder(INDEX_VALID) + rc, out = self._query(os.path.join("nonexistent", "file.pem")) + self.assertNotEqual(rc, 0) + self.assertRegex(out, + re.compile("fail|error|not found|unable|could not|" + "no such", + re.IGNORECASE)) + + def test_07_invalid_issuer_file(self): + self._start_responder(INDEX_VALID) + cmd = [ + self.CLIENT_BIN, "ocsp", + "-issuer", os.path.join("nonexistent", "issuer.pem"), + "-cert", os.path.join(CERTS_DIR, "server-cert.pem"), + "-CAfile", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-url", f"http://127.0.0.1:{self.PORT}", + ] + r = subprocess.run(cmd, capture_output=True, text=True, + stdin=subprocess.DEVNULL, timeout=30) + self.assertNotEqual(r.returncode, 0) + self.assertRegex(r.stdout + r.stderr, + re.compile("fail|error|unable|issuer|could not|" + "no such", + re.IGNORECASE)) + + # -- Delegated responder -- + + def test_08_delegated_responder(self): + self._start_responder( + INDEX_VALID, + rsigner=os.path.join(CERTS_DIR, "ocsp-responder-cert.pem"), + rkey=os.path.join(CERTS_DIR, "ocsp-responder-key.pem")) + rc, out = self._query("server-cert.pem") + self.assertEqual(rc, 0, f"delegated responder failed: {out}") + self.assertIn("good", out.lower()) + + def test_09_delegated_no_nonce(self): + self._start_responder( + INDEX_VALID, + rsigner=os.path.join(CERTS_DIR, "ocsp-responder-cert.pem"), + rkey=os.path.join(CERTS_DIR, "ocsp-responder-key.pem")) + rc, out = self._query("server-cert.pem", ["-no_nonce"]) + self.assertEqual(rc, 0, f"delegated no_nonce failed: {out}") + self.assertIn("good", out.lower()) + + def test_10_delegated_revoked(self): + self._start_responder( + INDEX_VALID + INDEX_REVOKED, + rsigner=os.path.join(CERTS_DIR, "ocsp-responder-cert.pem"), + rkey=os.path.join(CERTS_DIR, "ocsp-responder-key.pem")) + rc, out = self._query("server-revoked-cert.pem") + self.assertRegex(out, re.compile("revoked", re.IGNORECASE)) + + # -- Unreachable responder -- + + def test_11_unreachable_responder(self): + # Make sure responder is stopped + if self._responder: + self._responder.stop() + self.__class__._responder = None + + rc, out = self._query("server-cert.pem") + self.assertNotEqual(rc, 0) + self.assertRegex(out, + re.compile("fail|error|connect|timeout|refused", + re.IGNORECASE)) + + # -- Graceful shutdown (wolfssl responder only) -- + + def test_12_graceful_shutdown(self): + if self.RESPONDER_BIN != WOLFSSL_BIN: + self.skipTest("graceful shutdown only checked for wolfssl") + + resp = self._start_responder(INDEX_VALID, nrequest=1) + # Send one request to trigger the nrequest limit + self._query("server-cert.pem") + time.sleep(0.5) # let responder shut down + log = resp.read_log() + self.assertIn("wolfssl exiting gracefully", log) + + +# Concrete test classes for each client/responder combination. +# Each gets a unique port to avoid conflicts if run in parallel. + +class TestWolfsslClientWolfsslResponder(_OCSPInteropBase): + CLIENT_BIN = WOLFSSL_BIN + RESPONDER_BIN = WOLFSSL_BIN + PORT = OCSP_PORT_BASE + + +@unittest.skipUnless(HAS_OPENSSL, "openssl not available") +class TestWolfsslClientOpensslResponder(_OCSPInteropBase): + CLIENT_BIN = WOLFSSL_BIN + RESPONDER_BIN = "openssl" + PORT = OCSP_PORT_BASE + 1 + + +@unittest.skipUnless(HAS_OPENSSL, "openssl not available") +class TestOpensslClientWolfsslResponder(_OCSPInteropBase): + CLIENT_BIN = "openssl" + RESPONDER_BIN = WOLFSSL_BIN + PORT = OCSP_PORT_BASE + 2 + + +@unittest.skipUnless(HAS_OPENSSL, "openssl not available") +class TestOpensslClientOpensslResponder(_OCSPInteropBase): + CLIENT_BIN = "openssl" + RESPONDER_BIN = "openssl" + PORT = OCSP_PORT_BASE + 3 + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ocsp/ocsp-test.sh b/tests/ocsp/ocsp-test.sh deleted file mode 100755 index d34e77c4..00000000 --- a/tests/ocsp/ocsp-test.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/bash - -# Consolidated OCSP interoperability test -# Runs all test combinations in series to avoid port conflicts - -# Exit 77 to indicate test was skipped -# Exit 99 to indicate test failed -# Exit 0 to indicate test passed - -echo "======================================" -echo "OCSP Interoperability Test Suite" -echo "======================================" - -if ! ./wolfssl ocsp -help &> /dev/null; then - echo "ocsp not supported, skipping test" - exit 77 -fi - -# Track overall results -TOTAL=0 -PASSED=0 -SKIPPED=0 -FAILED=0 - -run_test() { - local client=$1 - local responder=$2 - local test_name="$client-$responder" - - echo "" - echo "Running: $test_name" - echo "--------------------------------------" - - TOTAL=$((TOTAL + 1)) - - export OCSP_CLIENT="$client" - export OCSP_RESPONDER="$responder" - - "$(dirname "$0")/ocsp-interop-test.sh" - local result=$? - - if [ $result -eq 0 ]; then - echo "✓ $test_name: PASSED" - PASSED=$((PASSED + 1)) - elif [ $result -eq 77 ]; then - echo "⊘ $test_name: SKIPPED" - SKIPPED=$((SKIPPED + 1)) - else - echo "✗ $test_name: FAILED (exit $result)" - FAILED=$((FAILED + 1)) - fi -} - -# Run all test combinations in series -run_test "./wolfssl" "openssl" -run_test "openssl" "./wolfssl" -run_test "./wolfssl" "./wolfssl" -# Running this config too to make sure the script works -run_test "openssl" "openssl" - -# Print summary -echo "" -echo "======================================" -echo "Test Summary" -echo "======================================" -echo "Total: $TOTAL" -echo "Passed: $PASSED" -echo "Skipped: $SKIPPED" -echo "Failed: $FAILED" -echo "======================================" - -# Return appropriate exit code -if [ $FAILED -gt 0 ]; then - exit 99 -elif [ $PASSED -eq 0 ]; then - # All tests were skipped - exit 77 -else - exit 0 -fi - - diff --git a/tests/pkcs/include.am b/tests/pkcs/include.am index 170cb88a..25759edb 100644 --- a/tests/pkcs/include.am +++ b/tests/pkcs/include.am @@ -2,7 +2,7 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/pkcs/pkcs12-test.sh -dist_noinst_SCRIPTS+=tests/pkcs/pkcs8-test.sh -dist_noinst_SCRIPTS+=tests/pkcs/pkcs7-test.sh +dist_noinst_SCRIPTS+=tests/pkcs/pkcs12-test.py +dist_noinst_SCRIPTS+=tests/pkcs/pkcs8-test.py +dist_noinst_SCRIPTS+=tests/pkcs/pkcs7-test.py diff --git a/tests/pkcs/pkcs12-test.py b/tests/pkcs/pkcs12-test.py new file mode 100644 index 00000000..c162f9de --- /dev/null +++ b/tests/pkcs/pkcs12-test.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""PKCS12 tests for wolfCLU.""" + +import os +import subprocess +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl + +P12_FILE = os.path.join(CERTS_DIR, "test-servercert.p12") + + +class Pkcs12Test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + # Skip FIPS builds + r = run_wolfssl("-v") + if "FIPS" in (r.stdout + r.stderr): + raise unittest.SkipTest("FIPS build") + + r = run_wolfssl("pkcs12", "-nodes", "-passin", 'pass:wolfSSL test', + "-passout", "pass:", "-in", P12_FILE) + combined = r.stdout + r.stderr + if "Recompile wolfSSL with PKCS12 support" in combined: + raise unittest.SkipTest("PKCS12 support not compiled in") + + def test_nocerts(self): + r = subprocess.run( + [WOLFSSL_BIN, "pkcs12", "-nodes", "-nocerts", + "-passin", "stdin", "-passout", "pass:", "-in", P12_FILE], + input=b"wolfSSL test", capture_output=True, text=False, + timeout=60, + ) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertNotIn(b"CERTIFICATE", r.stdout) + + def test_nokeys(self): + r = subprocess.run( + [WOLFSSL_BIN, "pkcs12", "-nokeys", + "-passin", "stdin", "-passout", "pass:", "-in", P12_FILE], + input=b"wolfSSL test", capture_output=True, text=False, + timeout=60, + ) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertNotIn(b"KEY", r.stdout) + + def test_pass_on_cmdline(self): + r = run_wolfssl("pkcs12", "-nodes", "-passin", 'pass:wolfSSL test', + "-passout", "pass:", "-in", P12_FILE) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_nocerts_with_passout(self): + r = subprocess.run( + [WOLFSSL_BIN, "pkcs12", "-passin", "stdin", "-passout", "pass:", + "-in", P12_FILE, "-nocerts"], + input=b"wolfSSL test", capture_output=True, text=False, + timeout=60, + ) + self.assertEqual(r.returncode, 0, r.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pkcs/pkcs12-test.sh b/tests/pkcs/pkcs12-test.sh deleted file mode 100755 index 9736eac3..00000000 --- a/tests/pkcs/pkcs12-test.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -# Is this a FIPS build? -if ./wolfssl -v 2>&1 | grep -q FIPS; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -RESULT=`./wolfssl pkcs12 -nodes -passin pass:"wolfSSL test" -passout pass: -in ./certs/test-servercert.p12 2>&1` -echo "$RESULT" | grep "Recompile wolfSSL with PKCS12 support" -if [ $? == 0 ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -run() { - if [ -z "$2" ]; then - if [ -z "$2" ]; then - RESULT=`eval $1` - else - RESULT=`echo "$2" | ./wolfssl $1` - fi - else - RESULT=`echo "$3" | echo "$2" | ./wolfssl $1` - fi - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run "pkcs12 -nodes -nocerts -passin stdin -passout pass: -in ./certs/test-servercert.p12" "wolfSSL test" - -#check that no certs were printed -echo $RESULT | grep "CERTIFICATE" -if [ $? == 0 ]; then - echo "ERROR found a cert with -nocerts option" - exit 99 -fi - -run "pkcs12 -nokeys -passin stdin -passout pass: -in ./certs/test-servercert.p12" "wolfSSL test" - -#check that no keys were printed -echo $RESULT | grep "KEY" -if [ $? == 0 ]; then - echo "ERROR found a key with -nokeys option" - exit 99 -fi - -run "./wolfssl pkcs12 -nodes -passin pass:\"wolfSSL test\" -passout pass: -in ./certs/test-servercert.p12" - -run "pkcs12 -passin stdin -passout pass: -in ./certs/test-servercert.p12 -nocerts" "wolfSSL test" "wolfSSL out test password" - -echo "Done" -exit 0 diff --git a/tests/pkcs/pkcs7-test.py b/tests/pkcs/pkcs7-test.py new file mode 100644 index 00000000..9c01bc6d --- /dev/null +++ b/tests/pkcs/pkcs7-test.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""PKCS7 tests for wolfCLU.""" + +import os +import subprocess +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl + + +class Pkcs7Test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + r = run_wolfssl("pkcs7", "-inform", "DER", "-in", + os.path.join(CERTS_DIR, "signed.p7b")) + combined = r.stdout + r.stderr + if "Recompile wolfSSL with PKCS7 support" in combined: + raise unittest.SkipTest("PKCS7 support not compiled in") + + def test_print_certs(self): + r = run_wolfssl("pkcs7", "-inform", "DER", "-print_certs", + "-in", os.path.join(CERTS_DIR, "signed.p7b")) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("CERTIFICATE", r.stdout) + + def test_der_to_pem(self): + r = run_wolfssl("pkcs7", "-inform", "DER", + "-in", os.path.join(CERTS_DIR, "signed.p7b"), + "-outform", "PEM") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("BEGIN PKCS7", r.stdout) + + def test_pem_to_der(self): + # Output is binary DER, so avoid text decoding + r = subprocess.run( + [WOLFSSL_BIN, "pkcs7", "-inform", "PEM", + "-in", os.path.join(CERTS_DIR, "signed.p7s"), + "-outform", "DER"], + capture_output=True, stdin=subprocess.DEVNULL, + timeout=60, + ) + self.assertEqual(r.returncode, 0, r.stderr) + + @unittest.skipIf(sys.platform == "win32", + "binary DER stdin is unreliable on Windows") + def test_stdin_input(self): + p7b_path = os.path.join(CERTS_DIR, "signed.p7b") + with open(p7b_path, "rb") as f: + data = f.read() + + r = subprocess.run( + [WOLFSSL_BIN, "pkcs7", "-inform", "DER"], + input=data, capture_output=True, text=False, + timeout=60, + ) + self.assertIn(b"BEGIN PKCS7", r.stdout + r.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pkcs/pkcs7-test.sh b/tests/pkcs/pkcs7-test.sh deleted file mode 100755 index d4f8751a..00000000 --- a/tests/pkcs/pkcs7-test.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -RESULT=`./wolfssl pkcs7 -inform DER -in certs/signed.p7b 2>&1` -echo "$RESULT" | grep "Recompile wolfSSL with PKCS7 support" -if [ $? == 0 ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -run() { - RESULT=`./wolfssl $1` - - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run "pkcs7 -inform DER -print_certs -in certs/signed.p7b" - -#check that certs were printed -echo $RESULT | grep "CERTIFICATE" -if [ $? != 0 ]; then - echo "ERROR didn't find cert with -print_certs option" - exit 99 -fi - -#check der to pem -run "pkcs7 -inform DER -in certs/signed.p7b -outform PEM" - -echo $RESULT | grep "BEGIN PKCS7" -if [ $? != 0 ]; then - echo "ERROR didn't PKCS7 PEM header in output" - exit 99 -fi - -#check pem to der -run "pkcs7 -inform PEM -in certs/signed.p7s -outform DER" - -#check stdin input -RESULT=`cat certs/signed.p7b | ./wolfssl pkcs7 -inform DER` -echo $RESULT | grep "BEGIN PKCS7" -if [ $? != 0 ]; then - echo "Couldn't parse PKCS7 from stdin" - exit 99 -fi - -echo "Done" -exit 0 diff --git a/tests/pkcs/pkcs8-test.py b/tests/pkcs/pkcs8-test.py new file mode 100644 index 00000000..70f20e9f --- /dev/null +++ b/tests/pkcs/pkcs8-test.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""PKCS8 tests for wolfCLU.""" + +import filecmp +import os +import subprocess +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl + + +def _is_fips(): + r = run_wolfssl("-v") + return "FIPS" in (r.stdout + r.stderr) + + +class Pkcs8Test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + r = run_wolfssl("pkcs8", "-in", + os.path.join(CERTS_DIR, "server-keyEnc.pem"), + "-passin", "pass:yassl123") + combined = r.stdout + r.stderr + if "Recompile wolfSSL with PKCS8 support" in combined: + raise unittest.SkipTest("PKCS8 support not compiled in") + + cls.is_fips = _is_fips() + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def test_decrypt_and_convert(self): + key_pem = "key.pem" + pkcs1_pem = "pkcs1.pem" + key_enc_der = "keyEnc.der" + self._cleanup(key_pem, pkcs1_pem, key_enc_der) + + if not self.is_fips: + r = run_wolfssl("pkcs8", "-in", + os.path.join(CERTS_DIR, "server-keyEnc.pem"), + "-passin", "pass:yassl123", + "-outform", "DER", "-out", key_enc_der) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkcs8", "-in", key_enc_der, "-inform", "DER", + "-outform", "PEM", "-out", key_pem) + self.assertEqual(r.returncode, 0, r.stderr) + else: + r = run_wolfssl("pkcs8", "-in", + os.path.join(CERTS_DIR, "server-key.pem"), + "-outform", "PEM", "-out", key_pem) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkcs8", "-in", key_pem, "-topk8", "-nocrypt") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkcs8", "-in", key_pem, "-traditional", + "-out", pkcs1_pem) + self.assertEqual(r.returncode, 0, r.stderr) + + self.assertTrue( + filecmp.cmp(os.path.join(CERTS_DIR, "server-key.pem"), + pkcs1_pem, shallow=False), + "server-key.pem -traditional check failed") + + @unittest.skipIf(_is_fips(), "skipped in FIPS builds") + def test_stdin_input(self): + pem_path = os.path.join(CERTS_DIR, "server-keyEnc.pem") + with open(pem_path, "rb") as f: + data = f.read() + + r = subprocess.run( + [WOLFSSL_BIN, "pkcs8", "-passin", "pass:yassl123"], + input=data, capture_output=True, text=False, + timeout=60, + ) + self.assertIn(b"BEGIN PRIVATE", r.stdout + r.stderr) + + @unittest.skipIf(_is_fips(), "skipped in FIPS builds") + def test_fail_wrong_input(self): + r = run_wolfssl("pkcs8", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-passin", "pass:yassl123") + self.assertNotEqual(r.returncode, 0) + + @unittest.skipIf(_is_fips(), "skipped in FIPS builds") + def test_fail_wrong_password(self): + r = run_wolfssl("pkcs8", "-in", + os.path.join(CERTS_DIR, "server-keyEnc.pem"), + "-passin", "pass:wrongPass") + self.assertNotEqual(r.returncode, 0) + + @unittest.skipIf(_is_fips(), "skipped in FIPS builds") + def test_fail_wrong_format(self): + r = run_wolfssl("pkcs8", "-in", + os.path.join(CERTS_DIR, "server-keyEnc.pem"), + "-inform", "DER", "-passin", "pass:yassl123") + self.assertNotEqual(r.returncode, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pkcs/pkcs8-test.sh b/tests/pkcs/pkcs8-test.sh deleted file mode 100755 index 89e32123..00000000 --- a/tests/pkcs/pkcs8-test.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -# Is this a FIPS build? -IS_FIPS=0 -if ./wolfssl -v 2>&1 | grep -q FIPS; then - IS_FIPS=1 -fi - -RESULT=`./wolfssl pkcs8 -in certs/server-keyEnc.pem -passin pass:yassl123 2>&1` -echo "$RESULT" | grep "Recompile wolfSSL with PKCS8 support" -if [ $? == 0 ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -run() { - RESULT=`./wolfssl $1` - - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - - if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -if [ ${IS_FIPS} != "1" ]; then - # Can only decrypt server-keyEnc.pem using DES if not a FIPS build - run "pkcs8 -in certs/server-keyEnc.pem -passin pass:yassl123 -outform DER -out keyEnc.der" - run "pkcs8 -in keyEnc.der -inform DER -outform PEM -out key.pem" -else - run "pkcs8 -in certs/server-key.pem -outform PEM -out key.pem" -fi - -run "pkcs8 -in key.pem -topk8 -nocrypt" - -run "pkcs8 -in key.pem -traditional -out pkcs1.pem" - -diff "./certs/server-key.pem" "./pkcs1.pem" &> /dev/null -if [ $? != 0 ]; then - echo "server-key.pem -traditional check failed" - exit 99 -fi - -rm -rf pkcs1.pem -rm -rf key.pem -rm -rf keyEnc.der - -if [ ${IS_FIPS} != "1" ]; then - #check stdin input - RESULT=`cat certs/server-keyEnc.pem | ./wolfssl pkcs8 -passin pass:yassl123` - echo $RESULT | grep "BEGIN PRIVATE" - if [ $? != 0 ]; then - echo "Couldn't parse PKCS8 from stdin" - exit 99 - fi - - run_fail "pkcs8 -in certs/server-cert.pem -passin pass:yassl123" - - run_fail "pkcs8 -in certs/server-keyEnc.pem -passin pass:wrongPass" - - run_fail "pkcs8 -in certs/server-keyEnc.pem -inform DER -passin pass:yassl123" -fi - -echo "Done" -exit 0 diff --git a/tests/pkey/ecparam-test.py b/tests/pkey/ecparam-test.py new file mode 100644 index 00000000..ef1f9478 --- /dev/null +++ b/tests/pkey/ecparam-test.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""EC parameter tests for wolfCLU.""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, run_wolfssl + + +def _get_curve_names(): + """Parse available curve names from ecparam -help output.""" + r = run_wolfssl("ecparam", "-help") + combined = r.stdout + r.stderr + in_names = False + names = [] + for line in combined.splitlines(): + if "name options" in line: + in_names = True + continue + if in_names: + name = line.strip() + if name and "SAKKE" not in name: + names.append(name) + return names + + +class EcparamTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def test_genkey_and_text(self): + self._cleanup("ecparam.key") + + r = run_wolfssl("ecparam", "-genkey", "-name", "secp384r1", + "-out", "ecparam.key") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("ecparam", "-text", "-in", "ecparam.key") + self.assertEqual(r.returncode, 0, r.stderr) + expected = ("Curve Name : SECP384R1\n" + "-----BEGIN EC PARAMETERS-----\n" + "BgUrgQQAIg==\n" + "-----END EC PARAMETERS-----") + self.assertEqual(r.stdout.strip(), expected) + + def test_text_existing_key(self): + r = run_wolfssl("ecparam", "-text", "-in", + os.path.join(CERTS_DIR, "ecc-key.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + expected = ("Curve Name : SECP256R1\n" + "-----BEGIN EC PARAMETERS-----\n" + "BggqhkjOPQMBBw==\n" + "-----END EC PARAMETERS-----") + self.assertEqual(r.stdout.strip(), expected) + + def test_pem_to_der(self): + self._cleanup("ecc-key.der") + + r = run_wolfssl("ecparam", "-in", + os.path.join(CERTS_DIR, "ecc-key.pem"), + "-out", "ecc-key.der", "-outform", "der") + self.assertEqual(r.returncode, 0, r.stderr) + + def test_fail_der_params_only(self): + """Reading DER with parameters only (no key) is not yet supported.""" + self._cleanup("ecc-key.der", "ecc-key.pem") + + r = run_wolfssl("ecparam", "-in", + os.path.join(CERTS_DIR, "ecc-key.pem"), + "-out", "ecc-key.der", "-outform", "der") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("ecparam", "-in", "ecc-key.der", "-inform", "der", + "-out", "ecc-key.pem", "-outform", "pem") + self.assertNotEqual(r.returncode, 0) + + def test_genkey_der(self): + self._cleanup("ecc-key.der") + + r = run_wolfssl("ecparam", "-genkey", "-out", "ecc-key.der", + "-outform", "der") + self.assertEqual(r.returncode, 0, r.stderr) + + def test_fail_non_ecc_key(self): + r = run_wolfssl("ecparam", "-in", + os.path.join(CERTS_DIR, "ca-key.pem"), "-text") + self.assertNotEqual(r.returncode, 0) + + def test_all_curves_ecparam(self): + """Generate key for each supported curve and verify text output.""" + names = _get_curve_names() + self.assertTrue(len(names) > 0, "no curve names found") + + for name in names: + with self.subTest(curve=name): + self._cleanup("tmp_ecparam.key", "tmp_ecparam_text") + + r = run_wolfssl("ecparam", "-genkey", "-name", name, + "-out", "tmp_ecparam.key") + self.assertEqual(r.returncode, 0, + f"genkey {name}: {r.stderr}") + + r = run_wolfssl("ecparam", "-text", "-in", "tmp_ecparam.key", + "-out", "tmp_ecparam_text") + self.assertEqual(r.returncode, 0, + f"text {name}: {r.stderr}") + + with open("tmp_ecparam_text", "r") as f: + text = f.read() + self.assertIn(name, text, + f"curve name {name} not in text output") + + def test_fail_bad_curve_name(self): + self._cleanup("tmp_ecparam.key") + + r = run_wolfssl("ecparam", "-genkey", "-name", "bad_curve_name", + "-out", "tmp_ecparam.key") + self.assertNotEqual(r.returncode, 0) + self.assertFalse(os.path.exists("tmp_ecparam.key"), + "key file should not be created for bad curve") + + def test_all_curves_genkey(self): + """Re-run curve test using the genkey command.""" + names = _get_curve_names() + self.assertTrue(len(names) > 0, "no curve names found") + + for name in names: + with self.subTest(curve=name): + self._cleanup("tmp_ecparam.priv", "tmp_ecparam.pub", + "tmp_ecparam_text") + + r = run_wolfssl("genkey", "ecc", "-name", name, + "-outform", "PEM", "-out", "tmp_ecparam") + self.assertEqual(r.returncode, 0, + f"genkey ecc {name}: {r.stderr}") + + r = run_wolfssl("ecparam", "-text", "-in", "tmp_ecparam.priv", + "-out", "tmp_ecparam_text") + self.assertEqual(r.returncode, 0, + f"text {name}: {r.stderr}") + + with open("tmp_ecparam_text", "r") as f: + text = f.read() + self.assertIn(name, text, + f"curve name {name} not in text output") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pkey/ecparam-test.sh b/tests/pkey/ecparam-test.sh deleted file mode 100755 index 14360ad6..00000000 --- a/tests/pkey/ecparam-test.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -run() { - RESULT=`./wolfssl $1` - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run "ecparam -genkey -name secp384r1 -out ecparam.key" -run "ecparam -text -in ecparam.key" -EXPECTED="Curve Name : SECP384R1 ------BEGIN EC PARAMETERS----- -BgUrgQQAIg== ------END EC PARAMETERS-----" -if [ "$RESULT" != "$EXPECTED" ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi -rm -f ecparam.key - -run "ecparam -text -in ./certs/ecc-key.pem" -EXPECTED="Curve Name : SECP256R1 ------BEGIN EC PARAMETERS----- -BggqhkjOPQMBBw== ------END EC PARAMETERS-----" -if [ "$RESULT" != "$EXPECTED" ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi - -# pem -> der -run "ecparam -in certs/ecc-key.pem -out ecc-key.der -outform der" - -# not yet supported reading only parameters with no key -run_fail "ecparam -in ecc-key.der -inform der -out ecc-key.pem -outform pem" - -run "ecparam -genkey -out ecc-key.der -outform der" -rm -f ecc-key.der - -run_fail "ecparam -in certs/ca-key.pem -text" - - -# get all possible curve name types and test @TODO leaving out SAKKE for now -NAMES=`./wolfssl ecparam -help | grep -A 100 "name options" | tr -d '[:blank:]' | grep -v "options" | grep -v "SAKKE"` - -for name in $NAMES; do - CURRENT="${name//[$'\t\r\n']}" - run "ecparam -genkey -name $CURRENT -out tmp_ecparam.key" - run "ecparam -text -in tmp_ecparam.key -out tmp_ecparam_text" - printf "grep $CURRENT tmp_ecparam_text\n" - grep $CURRENT tmp_ecparam_text - if [ "$?" != "0" ]; then - echo Failed when testing curve name $CURRENT - exit 99 - fi - rm -rf tmp_ecparam.key - rm -rf tmp_ecparam_text -done - -# test an unknown curve name -run_fail "ecparam -genkey -name bad_curve_name -out tmp_ecparam.key" -if [ -f tmp_ecparam.key ]; then - echo File tmp_ecparam.key should not have been created - exit 99 -fi - -# re-run the test but now with genkey command -for name in $NAMES; do - CURRENT="${name//[$'\t\r\n']}" - run "genkey ecc -name $CURRENT -outform PEM -out tmp_ecparam" - run "ecparam -text -in tmp_ecparam.priv -out tmp_ecparam_text" - printf "grep $CURRENT tmp_ecparam_text\n" - grep $CURRENT tmp_ecparam_text - if [ "$?" != "0" ]; then - echo Failed when testing curve name $CURRENT - exit 99 - fi - rm -rf tmp_ecparam.priv - rm -rf tmp_ecparam.pub - rm -rf tmp_ecparam_text -done - -echo "Done" -exit 0 - diff --git a/tests/pkey/include.am b/tests/pkey/include.am index 9e1d65ac..9b9b8214 100644 --- a/tests/pkey/include.am +++ b/tests/pkey/include.am @@ -2,8 +2,8 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/pkey/pkey-test.sh -dist_noinst_SCRIPTS+=tests/pkey/ecparam-test.sh -dist_noinst_SCRIPTS+=tests/pkey/rsa-test.sh +dist_noinst_SCRIPTS+=tests/pkey/pkey-test.py +dist_noinst_SCRIPTS+=tests/pkey/ecparam-test.py +dist_noinst_SCRIPTS+=tests/pkey/rsa-test.py diff --git a/tests/pkey/pkey-test.py b/tests/pkey/pkey-test.py new file mode 100644 index 00000000..e88021f5 --- /dev/null +++ b/tests/pkey/pkey-test.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +"""pkey tests for wolfCLU.""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, run_wolfssl + +ECC_PUBKEY_PEM = """\ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuzOsTCdQSsZKpQTDPN6fNttyLc6U +6iv6yyAJOSwW6GEC6a9N0wKTmjFbl5Ihf/DPGNqREQI0huggWDMLgDSJ2A== +-----END PUBLIC KEY-----""" + +ECC_PRIVKEY_PEM = """\ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEW2aQJznGyFoThbcujox6zEA41TNQT6bCjcNI3hqAmMoAoGCCqGSM49 +AwEHoUQDQgAEuzOsTCdQSsZKpQTDPN6fNttyLc6U6iv6yyAJOSwW6GEC6a9N0wKT +mjFbl5Ihf/DPGNqREQI0huggWDMLgDSJ2A== +-----END EC PRIVATE KEY-----""" + + +class PkeyTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def test_pubin_ecc(self): + r = run_wolfssl("pkey", "-pubin", "-in", + os.path.join(CERTS_DIR, "ecc-keyPub.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), ECC_PUBKEY_PEM) + + def test_fail_pubin_private_key(self): + r = run_wolfssl("pkey", "-pubin", "-in", + os.path.join(CERTS_DIR, "ecc-key.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_pem_der_pem_private(self): + self._cleanup("ecc.der", "ecc.pem") + + r = run_wolfssl("pkey", "-in", + os.path.join(CERTS_DIR, "ecc-key.pem"), + "-outform", "der", "-out", "ecc.der") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkey", "-in", "ecc.der", "-inform", "der", + "-outform", "pem", "-out", "ecc.pem") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkey", "-in", "ecc.pem") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), ECC_PRIVKEY_PEM) + + def test_pem_der_pem_public(self): + self._cleanup("ecc.der", "ecc.pem") + + r = run_wolfssl("pkey", "-pubin", "-in", + os.path.join(CERTS_DIR, "ecc-keyPub.pem"), + "-outform", "der", "-out", "ecc.der") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkey", "-pubin", "-in", "ecc.der", "-inform", "der", + "-outform", "pem", "-out", "ecc.pem") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkey", "-pubin", "-in", "ecc.pem") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), ECC_PUBKEY_PEM) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pkey/pkey-test.sh b/tests/pkey/pkey-test.sh deleted file mode 100755 index e8d4a3dd..00000000 --- a/tests/pkey/pkey-test.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -run() { - RESULT=`./wolfssl $1` - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run "pkey -pubin -in ./certs/ecc-keyPub.pem" -EXPECTED="-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuzOsTCdQSsZKpQTDPN6fNttyLc6U -6iv6yyAJOSwW6GEC6a9N0wKTmjFbl5Ihf/DPGNqREQI0huggWDMLgDSJ2A== ------END PUBLIC KEY-----" -if [ "$RESULT" != "$EXPECTED" ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi -run_fail "pkey -pubin -in ./certs/ecc-key.pem" - -# pem -> der -> pem -run "pkey -in ./certs/ecc-key.pem -outform der -out ecc.der" -run "pkey -in ecc.der -inform der -outform pem -out ecc.pem" -run "pkey -in ecc.pem" -EXPECTED="-----BEGIN EC PRIVATE KEY----- -MHcCAQEEIEW2aQJznGyFoThbcujox6zEA41TNQT6bCjcNI3hqAmMoAoGCCqGSM49 -AwEHoUQDQgAEuzOsTCdQSsZKpQTDPN6fNttyLc6U6iv6yyAJOSwW6GEC6a9N0wKT -mjFbl5Ihf/DPGNqREQI0huggWDMLgDSJ2A== ------END EC PRIVATE KEY-----" -if [ "$RESULT" != "$EXPECTED" ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi -rm -f ecc.der -rm -f ecc.pem - -# pubkey pem -> der -> pem -run "pkey -pubin -in ./certs/ecc-keyPub.pem -outform der -out ecc.der" -run "pkey -pubin -in ecc.der -inform der -outform pem -out ecc.pem" -run "pkey -pubin -in ecc.pem" -EXPECTED="-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuzOsTCdQSsZKpQTDPN6fNttyLc6U -6iv6yyAJOSwW6GEC6a9N0wKTmjFbl5Ihf/DPGNqREQI0huggWDMLgDSJ2A== ------END PUBLIC KEY-----" -if [ "$RESULT" != "$EXPECTED" ]; then - echo "unexpected text output found" - echo "$RESULT" - exit 99 -fi -rm -f ecc.der -rm -f ecc.pem - -rm -rf ecc.pem ecc.der - -echo "Done" -exit 0 - diff --git a/tests/pkey/rsa-test.py b/tests/pkey/rsa-test.py new file mode 100644 index 00000000..8ba50907 --- /dev/null +++ b/tests/pkey/rsa-test.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""RSA key tests for wolfCLU.""" + +import filecmp +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, run_wolfssl + +RSA_PUBKEY_PEM = """\ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwJUI4VdB8nFtt9JFQScB +ZcZFrvK8JDC4lc4vTtb2HIi8fJ/7qGd//lycUXX3isoH5zUvj+G9e8AvfKtkqBf8 +yl17uuAh5XIuby6G2JVz2qwbU7lfP9cZDSVP4WNjUYsLZD+tQ7ilHFw0s64AoGPF +9n8LWWh4c6aMGKkCba/DGQEuuBDjxsxAtGmjRjNph27Euxem8+jdrXO8ey8htf1m +UQy9VLPhbV8cvCNz0QkDiRTSELlkwyrQoZZKvOHUGlvHoMDBY3gPRDcwMpaAMiOV +oXe6E9KXc+JdJclqDcM5YKS0sGlCQgnp2Ai8MyCzWCKnquvE4eZhg8XSlt/Z0E+t +1wIDAQAB +-----END PUBLIC KEY-----""" + + +def _is_fips(): + r = run_wolfssl("-v") + return "FIPS" in (r.stdout + r.stderr) + + +class RsaTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + cls.is_fips = _is_fips() + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def test_pem_to_pem(self): + out = "test-rsa.pem" + self._cleanup(out) + + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-key.pem"), + "-outform", "PEM", "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue( + filecmp.cmp(os.path.join(CERTS_DIR, "server-key.pem"), + out, shallow=False), + "PEM to PEM mismatch") + + def test_pem_to_der(self): + out = "test-rsa.der" + self._cleanup(out) + + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-key.pem"), + "-outform", "DER", "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue( + filecmp.cmp(os.path.join(CERTS_DIR, "server-key.der"), + out, shallow=False), + "PEM to DER mismatch") + + def test_fail_cert_as_key(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-cert.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_fail_rsapublickey_in_cert(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-RSAPublicKey_in") + self.assertNotEqual(r.returncode, 0) + + def test_fail_rsapublickey_in_privkey(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-key.pem"), + "-RSAPublicKey_in") + self.assertNotEqual(r.returncode, 0) + + def test_fail_pubin_cert(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-pubin") + self.assertNotEqual(r.returncode, 0) + + def test_fail_pubin_privkey(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-key.pem"), + "-pubin") + self.assertNotEqual(r.returncode, 0) + + def test_rsapublickey_in(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-RSAPublicKey_in") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), RSA_PUBKEY_PEM) + + def test_pubin(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-pubin") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), RSA_PUBKEY_PEM) + + @unittest.skipIf(_is_fips(), "skipped in FIPS builds") + def test_encrypted_key(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-keyEnc.pem"), + "-passin", "pass:yassl123") + self.assertEqual(r.returncode, 0, r.stderr) + + @unittest.skipIf(_is_fips(), "skipped in FIPS builds") + def test_fail_wrong_password(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-keyEnc.pem"), + "-passin", "pass:yassl12") + self.assertNotEqual(r.returncode, 0) + + @unittest.skipIf(_is_fips(), "skipped in FIPS builds") + def test_modulus_noout(self): + r = run_wolfssl("rsa", "-in", + os.path.join(CERTS_DIR, "server-keyEnc.pem"), + "-passin", "pass:yassl123", "-noout", "-modulus") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("Modulus", r.stdout) + self.assertNotIn("BEGIN", r.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pkey/rsa-test.sh b/tests/pkey/rsa-test.sh deleted file mode 100755 index 2bb0e6c9..00000000 --- a/tests/pkey/rsa-test.sh +++ /dev/null @@ -1,142 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -# Is this a FIPS build? -IS_FIPS=0 -if ./wolfssl -v 2>&1 | grep -q FIPS; then - IS_FIPS=1 -fi - -run() { - if [ -z "$2" ]; then - RESULT=`./wolfssl $1` - else - RESULT=`echo "$2" | ./wolfssl $1` - fi - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -run_fail() { - if [ -z "$2" ]; then - RESULT=`./wolfssl $1` - else - RESULT=`echo "$2" | ./wolfssl $1` - fi - if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl $1\"" - exit 99 - fi -} - -# Test PEM to PEM conversion -run "rsa -in ./certs/server-key.pem -outform PEM -out test-rsa.pem" -diff "./certs/server-key.pem" "test-rsa.pem" &> /dev/null -if [ $? == 1 ]; then - echo "unexpected pem output" - exit 99 -fi -rm -f test-rsa.pem - -# Test PEM to DER conversion -run "rsa -in ./certs/server-key.pem -outform DER -out test-rsa.der" -diff "./certs/server-key.der" "test-rsa.der" &> /dev/null -if [ $? == 1 ]; then - echo "unexpected der output" - exit 99 -fi -rm -f test-rsa.der - -# Test failures -run_fail "rsa -in ./certs/server-cert.pem" - -# Test failures for -RSAPublicKey_in -run_fail "rsa -in ./certs/server-cert.pem -RSAPublicKey_in" -run_fail "rsa -in ./certs/server-key.pem -RSAPublicKey_in" - -# Test failures for -pubin -run_fail "rsa -in ./certs/server-cert.pem -pubin" -run_fail "rsa -in ./certs/server-key.pem -pubin" - -# Test success cases for -RSAPublicKey_in -run "rsa -in ./certs/server-keyPub.pem -RSAPublicKey_in" - -if [ ${IS_FIPS} != "1" ]; then - run "rsa -in ./certs/server-keyEnc.pem -passin pass:yassl123" - run_fail "rsa -in ./certs/server-keyEnc.pem -passin pass:yassl12" - - run "rsa -in ./certs/server-keyEnc.pem -passin pass:yassl123 -noout -modulus" -fi - -# Test success cases for -pubin -run "rsa -in ./certs/server-keyPub.pem -pubin" -if [ ${IS_FIPS} != "1" ]; then - run "rsa -in ./certs/server-keyEnc.pem -passin pass:yassl123" - run_fail "rsa -in ./certs/server-keyEnc.pem -passin pass:yassl12" - - run "rsa -in ./certs/server-keyEnc.pem -passin pass:yassl123 -noout -modulus" - - # Check that modulus was printed - echo $RESULT | grep "Modulus" - if [ $? != 0 ]; then - echo "ERROR with -modulus option" - exit 99 - fi - - # Check that key was not printed - echo $RESULT | grep "BEGIN" - if [ $? == 0 ]; then - echo "ERROR found a key with -modulus option" - exit 99 - fi -fi - -# Expexted result -RSAPublicKey_in -run "rsa -in ./certs/server-keyPub.pem -RSAPublicKey_in" -EXPECTED="-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwJUI4VdB8nFtt9JFQScB -ZcZFrvK8JDC4lc4vTtb2HIi8fJ/7qGd//lycUXX3isoH5zUvj+G9e8AvfKtkqBf8 -yl17uuAh5XIuby6G2JVz2qwbU7lfP9cZDSVP4WNjUYsLZD+tQ7ilHFw0s64AoGPF -9n8LWWh4c6aMGKkCba/DGQEuuBDjxsxAtGmjRjNph27Euxem8+jdrXO8ey8htf1m -UQy9VLPhbV8cvCNz0QkDiRTSELlkwyrQoZZKvOHUGlvHoMDBY3gPRDcwMpaAMiOV -oXe6E9KXc+JdJclqDcM5YKS0sGlCQgnp2Ai8MyCzWCKnquvE4eZhg8XSlt/Z0E+t -1wIDAQAB ------END PUBLIC KEY-----" -if [ "$RESULT" != "$EXPECTED" ]; then - echo "unexpected text output found for -RSAPublicKey_in" - echo "$RESULT" - exit 99 -fi - -# Expexted result -pubin -run "rsa -in ./certs/server-keyPub.pem -pubin" -EXPECTED1="-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwJUI4VdB8nFtt9JFQScB -ZcZFrvK8JDC4lc4vTtb2HIi8fJ/7qGd//lycUXX3isoH5zUvj+G9e8AvfKtkqBf8 -yl17uuAh5XIuby6G2JVz2qwbU7lfP9cZDSVP4WNjUYsLZD+tQ7ilHFw0s64AoGPF -9n8LWWh4c6aMGKkCba/DGQEuuBDjxsxAtGmjRjNph27Euxem8+jdrXO8ey8htf1m -UQy9VLPhbV8cvCNz0QkDiRTSELlkwyrQoZZKvOHUGlvHoMDBY3gPRDcwMpaAMiOV -oXe6E9KXc+JdJclqDcM5YKS0sGlCQgnp2Ai8MyCzWCKnquvE4eZhg8XSlt/Z0E+t -1wIDAQAB ------END PUBLIC KEY-----" -if [ "$RESULT" != "$EXPECTED1" ]; then - echo "unexpected text output found for -pubin" - echo "$RESULT" - exit 99 -fi - -echo "Done" -exit 0 diff --git a/tests/rand/include.am b/tests/rand/include.am index ebe516a5..5d7b69d9 100644 --- a/tests/rand/include.am +++ b/tests/rand/include.am @@ -2,5 +2,5 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/rand/rand-test.sh +dist_noinst_SCRIPTS+=tests/rand/rand-test.py diff --git a/tests/rand/rand-test.py b/tests/rand/rand-test.py new file mode 100644 index 00000000..455fa46e --- /dev/null +++ b/tests/rand/rand-test.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Random number generation tests for wolfCLU.""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import run_wolfssl + + +class RandTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + def test_base64_random(self): + r = run_wolfssl("rand", "-base64", "10") + self.assertEqual(r.returncode, 0, r.stderr) + + def test_base64_not_repeated(self): + r1 = run_wolfssl("rand", "-base64", "10") + self.assertEqual(r1.returncode, 0, r1.stderr) + + r2 = run_wolfssl("rand", "-base64", "10") + self.assertEqual(r2.returncode, 0, r2.stderr) + + self.assertNotEqual(r1.stdout, r2.stdout, + "back-to-back random calls should differ") + + def test_output_file(self): + out = "entropy.txt" + self.addCleanup(lambda: os.remove(out) + if os.path.exists(out) else None) + + r = run_wolfssl("rand", "-out", out, "20") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(os.path.isfile(out), "entropy.txt not created") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/rand/rand-test.sh b/tests/rand/rand-test.sh deleted file mode 100755 index bee3386d..00000000 --- a/tests/rand/rand-test.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -RESULT=`./wolfssl rand -base64 10` -if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl rand -base64 10\"" - exit 99 -fi - -RESULT2=`./wolfssl rand -base64 10` -if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl rand -base64 10\"" - exit 99 -fi - -if [ "$RESULT" == "$RESULT2" ]; then - echo "$RESULT == $RESULT2" - echo "Unlikely that a random 10 bytes will be same on back to back calls" - exit 99 -fi - -rm -f entropy.txt -RESULT=`./wolfssl rand -out entropy.txt 20` -if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl rand -base64 10\"" - exit 99 -fi - -if [ ! -f "entropy.txt" ]; then - echo "entropy.txt not created" - exit 99 -fi -rm -f entropy.txt - -echo "Done" - -exit 0 diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100755 index 00000000..befc2409 --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Test runner for wolfCLU Python tests. + +Discovers and runs all *-test.py files under the tests/ directory. +Intended for use on Windows where `make check` is not available. +""" + +import glob +import importlib.util +import os +import sys +import unittest + + +def load_tests_from_file(path): + """Load a unittest module from a file path (supports hyphens in names).""" + name = os.path.splitext(os.path.basename(path))[0].replace("-", "_") + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return unittest.TestLoader().loadTestsFromModule(module) + + +def main(): + # Run from the project root so tests can find ./wolfssl and ./certs + script_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.dirname(script_dir) + os.chdir(project_root) + + # Ensure tests can import the shared wolfclu_test helper + if script_dir not in sys.path: + sys.path.insert(0, script_dir) + + suite = unittest.TestSuite() + pattern = os.path.join(script_dir, "**", "*-test.py") + for test_file in sorted(glob.glob(pattern, recursive=True)): + suite.addTests(load_tests_from_file(test_file)) + + runner = unittest.TextTestRunner(verbosity=2, durations=5) + result = runner.run(suite) + sys.exit(0 if result.wasSuccessful() else 1) + + +if __name__ == "__main__": + main() diff --git a/tests/server/include.am b/tests/server/include.am index 22cea69b..3d7f5cab 100644 --- a/tests/server/include.am +++ b/tests/server/include.am @@ -2,5 +2,5 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/server/server-test.sh +dist_noinst_SCRIPTS+=tests/server/server-test.py diff --git a/tests/server/server-test.py b/tests/server/server-test.py new file mode 100644 index 00000000..7e3cb44f --- /dev/null +++ b/tests/server/server-test.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""TLS server/client communication test for wolfCLU.""" + +import os +import subprocess +import sys +import time +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR + + +class ServerClientTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if not os.path.isdir(CERTS_DIR): + raise unittest.SkipTest("certs directory not found") + + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + def test_server_client(self): + """Start s_server, connect with s_client, verify handshake.""" + readyfile = "readyfile" + if os.path.exists(readyfile): + os.remove(readyfile) + + # Start server in background + server = subprocess.Popen( + [WOLFSSL_BIN, "s_server", "-port", "11111", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-cert", os.path.join(CERTS_DIR, "server-cert.pem"), + "-noVerify", "-readyFile", readyfile], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdin=subprocess.DEVNULL, + ) + + try: + # Wait for server to be ready + for _ in range(200): + if os.path.exists(readyfile): + break + time.sleep(0.01) + else: + self.fail("s_server did not become ready") + + if os.path.exists(readyfile): + os.remove(readyfile) + + # Connect with client + client = subprocess.run( + [WOLFSSL_BIN, "s_client", "-connect", "127.0.0.1:11111", + "-CAfile", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-verify_return_error", "-disable_stdin_check"], + capture_output=True, stdin=subprocess.DEVNULL, timeout=30, + ) + self.assertEqual(client.returncode, 0, + f"s_client failed: {client.stderr}") + finally: + server.terminate() + server.wait(timeout=5) + server.stdout.close() + server.stderr.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/server/server-test.sh b/tests/server/server-test.sh deleted file mode 100755 index 6cdf8352..00000000 --- a/tests/server/server-test.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -READYFILE="readyfile" -rm -f $READYFILE - -# Build s_server in background -./wolfssl s_server -port 11111 -key ./certs/server-key.pem\ - -cert ./certs/server-cert.pem -noVerify -readyFile $READYFILE & -pid_server=$! - -counter=0 -# If readyfile exists, the server is considered ready. -while [ ! -e $READYFILE ] && [ "$counter" -lt 100 ]; do - ((counter++)) - sleep 0.01 -done -rm -f $READYFILE - -# Timeout -if [ "$counter" -eq 100 ]; then - echo "s_server was not built successfully." - kill $pid_server - exit 99 -fi - -./wolfssl s_client -connect 127.0.0.1:11111 -CAfile ./certs/ca-cert.pem\ - -verify_return_error -disable_stdin_check -if [ $? != 0 ] ; then - echo "test communication failed." - exit 99 -fi - -echo "Done" -exit 0 \ No newline at end of file diff --git a/tests/testEncDec/README.md b/tests/testEncDec/README.md deleted file mode 100644 index 06782e09..00000000 --- a/tests/testEncDec/README.md +++ /dev/null @@ -1,13 +0,0 @@ -To run ./test_aescbc_3des_cam.sh you need to have wolfssl configured with the -following: - - --enable-pwdbased - --enable-opensslextra - --enable-camellia - --enable-des3 -Additionally You will need to go to: - -/tests/somejunk - -and execute each of the .sh scripts to generate the respective test files. - diff --git a/tests/testEncDec/encdec-test.py b/tests/testEncDec/encdec-test.py new file mode 100644 index 00000000..e896dbae --- /dev/null +++ b/tests/testEncDec/encdec-test.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Encrypt/decrypt round-trip tests for various cipher algorithms.""" + +import filecmp +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import run_wolfssl + +# Small test input — created once, used by all tests +INPUT_FILE = "encdec_input.txt" +INPUT_DATA = "The quick brown fox jumps over the lazy dog.\n" * 100 + + +def _available_algos(): + """Parse available algorithms from -encrypt -help output.""" + r = run_wolfssl("-encrypt", "-help") + combined = r.stdout + r.stderr + algos = set() + for line in combined.splitlines(): + for token in line.split(): + if "-" in token and any(c.isdigit() for c in token): + algos.add(token) + return algos + + +_ALGOS = _available_algos() + + +class EncDecRoundtripTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + with open(INPUT_FILE, "w") as f: + f.write(INPUT_DATA) + + @classmethod + def tearDownClass(cls): + if os.path.exists(INPUT_FILE): + os.remove(INPUT_FILE) + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def _roundtrip(self, algo, password): + enc_file = f"enc_{algo.replace('-', '_')}.bin" + dec_file = f"dec_{algo.replace('-', '_')}.bin" + self._cleanup(enc_file, dec_file) + + r = run_wolfssl("-encrypt", algo, "-pwd", password, + "-in", INPUT_FILE, "-out", enc_file) + self.assertEqual(r.returncode, 0, + f"encrypt {algo} failed: {r.stderr}") + self.assertFalse(filecmp.cmp(INPUT_FILE, enc_file, shallow=False), + f"{algo} encrypted file is identical to input") + + r = run_wolfssl("-decrypt", algo, "-in", enc_file, + "-out", dec_file, "-pwd", password) + self.assertEqual(r.returncode, 0, + f"decrypt {algo} failed: {r.stderr}") + self.assertTrue(filecmp.cmp(INPUT_FILE, dec_file, shallow=False), + f"{algo} decrypted file does not match original") + + +@unittest.skipUnless("aes-cbc-128" in _ALGOS, "AES-CBC not available") +class AesCbcTest(EncDecRoundtripTest): + + def test_aes_cbc_128(self): + self._roundtrip("aes-cbc-128", "hello128") + + def test_aes_cbc_192(self): + self._roundtrip("aes-cbc-192", "hello192") + + def test_aes_cbc_256(self): + self._roundtrip("aes-cbc-256", "hello256") + + +@unittest.skipUnless("aes-ctr-128" in _ALGOS, "AES-CTR not available") +class AesCtrTest(EncDecRoundtripTest): + + def test_aes_ctr_128(self): + self._roundtrip("aes-ctr-128", "hello128") + + def test_aes_ctr_192(self): + self._roundtrip("aes-ctr-192", "hello192") + + def test_aes_ctr_256(self): + self._roundtrip("aes-ctr-256", "hello256") + + +@unittest.skipUnless("3des-cbc-56" in _ALGOS, "3DES-CBC not available") +class Des3CbcTest(EncDecRoundtripTest): + + def test_3des_cbc_56(self): + self._roundtrip("3des-cbc-56", "hello056") + + def test_3des_cbc_112(self): + self._roundtrip("3des-cbc-112", "hello112") + + def test_3des_cbc_168(self): + self._roundtrip("3des-cbc-168", "hello168") + + +@unittest.skipUnless("camellia-cbc-128" in _ALGOS, + "Camellia-CBC not available") +class CamelliaCbcTest(EncDecRoundtripTest): + + def test_camellia_cbc_128(self): + self._roundtrip("camellia-cbc-128", "hello128") + + def test_camellia_cbc_192(self): + self._roundtrip("camellia-cbc-192", "hello192") + + def test_camellia_cbc_256(self): + self._roundtrip("camellia-cbc-256", "hello256") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/testEncDec/include.am b/tests/testEncDec/include.am new file mode 100644 index 00000000..2cd9409a --- /dev/null +++ b/tests/testEncDec/include.am @@ -0,0 +1,6 @@ +# vim:ft=automake +# included from top level Makefile.am +# ALl path should be given relative to root directory + +dist_noinst_SCRIPTS+=tests/testEncDec/encdec-test.py + diff --git a/tests/testEncDec/test_aescbc_3des_cam.sh b/tests/testEncDec/test_aescbc_3des_cam.sh deleted file mode 100755 index 3be352b3..00000000 --- a/tests/testEncDec/test_aescbc_3des_cam.sh +++ /dev/null @@ -1,30 +0,0 @@ - -###### AES CBC ###### -wolfssl -encrypt aes-cbc-128 -pwd hello128 -in ../somejunk/somejunk_100000.txt -out enc_aes_cbc_128.txt -wolfssl -decrypt aes-cbc-128 -in enc_aes_cbc_128.txt -out dec_aes_cbc_128.txt -pwd hello128 - -wolfssl -encrypt aes-cbc-192 -pwd hello192 -in ../somejunk/somejunk_100000.txt -out enc_aes_cbc_192.txt -wolfssl -decrypt aes-cbc-192 -in enc_aes_cbc_192.txt -out dec_aes_cbc_192.txt -pwd hello192 - -wolfssl -encrypt aes-cbc-256 -pwd hello256 -in ../somejunk/somejunk_100000.txt -out enc_aes_cbc_256.txt -wolfssl -decrypt aes-cbc-256 -in enc_aes_cbc_256.txt -out dec_aes_cbc_256.txt -pwd hello256 - -###### DES CBC ###### -wolfssl -encrypt 3des-cbc-056 -pwd hello056 -in ../somejunk/somejunk_100000.txt -out enc_3des_cbc_056.txt -wolfssl -decrypt 3des-cbc-056 -in enc_3des_cbc_056.txt -out dec_3des_cbc_056.txt -pwd hello056 - -wolfssl -encrypt 3des-cbc-112 -pwd hello112 -in ../somejunk/somejunk_100000.txt -out enc_3des_cbc_112.txt -wolfssl -decrypt 3des-cbc-112 -in enc_3des_cbc_112.txt -out dec_3des_cbc_112.txt -pwd hello112 - -wolfssl -encrypt 3des-cbc-168 -pwd hello168 -in ../somejunk/somejunk_100000.txt -out enc_3des_cbc_168.txt -wolfssl -decrypt 3des-cbc-168 -in enc_3des_cbc_168.txt -out dec_3des_cbc_168.txt -pwd hello168 - -###### CAMELLIA CBC ###### -wolfssl -encrypt camellia-cbc-128 -pwd hello128 -in ../somejunk/somejunk_100000.txt -out enc_camellia_cbc_128.txt -wolfssl -decrypt camellia-cbc-128 -in enc_camellia_cbc_128.txt -out dec_camellia_cbc_128.txt -pwd hello128 - -wolfssl -encrypt camellia-cbc-192 -pwd hello192 -in ../somejunk/somejunk_100000.txt -out enc_camellia_cbc_192.txt -wolfssl -decrypt camellia-cbc-192 -in enc_camellia_cbc_192.txt -out dec_camellia_cbc_192.txt -pwd hello192 - -wolfssl -encrypt camellia-cbc-256 -pwd hello256 -in ../somejunk/somejunk_100000.txt -out enc_camellia_cbc_256.txt -wolfssl -decrypt camellia-cbc-256 -in enc_camellia_cbc_256.txt -out dec_camellia_cbc_256.txt -pwd hello256 diff --git a/tests/testEncDec/test_aesctr.sh b/tests/testEncDec/test_aesctr.sh deleted file mode 100755 index f03086e6..00000000 --- a/tests/testEncDec/test_aesctr.sh +++ /dev/null @@ -1,9 +0,0 @@ -###### AES CTR ###### -wolfssl -encrypt aes-ctr-128 -pwd hello128 -in ../somejunk/somejunk_100000.txt -out enc_aes_ctr_128.txt -wolfssl -decrypt aes-ctr-128 -in enc_aes_ctr_128.txt -out dec_aes_ctr_128.txt -pwd hello128 - -wolfssl -encrypt aes-ctr-192 -pwd hello192 -in ../somejunk/somejunk_100000.txt -out enc_aes_ctr_192.txt -wolfssl -decrypt aes-ctr-192 -in enc_aes_ctr_192.txt -out dec_aes_ctr_192.txt -pwd hello192 - -wolfssl -encrypt aes-ctr-256 -pwd hello256 -in ../somejunk/somejunk_100000.txt -out enc_aes_ctr_256.txt -wolfssl -decrypt aes-ctr-256 -in enc_aes_ctr_256.txt -out dec_aes_ctr_256.txt -pwd hello256 diff --git a/tests/wolfclu_test.py b/tests/wolfclu_test.py new file mode 100644 index 00000000..a92b3653 --- /dev/null +++ b/tests/wolfclu_test.py @@ -0,0 +1,48 @@ +"""Shared helpers for wolfCLU Python tests.""" + +import os +import platform +import subprocess + + +def _find_wolfssl_bin(): + """Locate the wolfssl binary, searching common build output paths.""" + if platform.system() == "Windows": + candidates = [ + os.path.join(".", "x64", "Debug", "wolfssl.exe"), + os.path.join(".", "x64", "Release", "wolfssl.exe"), + os.path.join(".", "Debug", "wolfssl.exe"), + os.path.join(".", "Release", "wolfssl.exe"), + os.path.join(".", "wolfssl.exe"), + ] + else: + candidates = [ + os.path.join(".", "wolfssl"), + ] + + for path in candidates: + if os.path.isfile(path): + return path + + # Fall back to first candidate; tests will get a clear FileNotFoundError + return candidates[0] + + +WOLFSSL_BIN = _find_wolfssl_bin() +CERTS_DIR = os.path.join(".", "certs") + + +def run_wolfssl(*args, stdin_data=None, timeout=60): + """Run the wolfssl binary with the given arguments. + + Returns a CompletedProcess instance. + A default timeout of 60 seconds prevents indefinite hangs in CI. + Network-facing tests (s_client, ocsp) manage their own timeouts. + """ + cmd = [WOLFSSL_BIN] + list(args) + kwargs = dict(capture_output=True, text=True, timeout=timeout) + if stdin_data is not None: + kwargs["input"] = stdin_data + else: + kwargs["stdin"] = subprocess.DEVNULL + return subprocess.run(cmd, **kwargs) diff --git a/tests/x509/CRL-verify-test.py b/tests/x509/CRL-verify-test.py new file mode 100644 index 00000000..5cbb9111 --- /dev/null +++ b/tests/x509/CRL-verify-test.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Tests for wolfssl crl (converted from CRL-verify-test.sh).""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, run_wolfssl + + +def _has_crl(): + """Check whether CRL support is compiled in.""" + r = run_wolfssl("crl", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + "-in", os.path.join(CERTS_DIR, "crl.pem")) + combined = r.stdout + r.stderr + return "recompile wolfSSL with CRL support" not in combined + + +def _has_crl_text(): + """Check whether CRL -text is available (not just 'print not available').""" + r = run_wolfssl("crl", "-in", os.path.join(CERTS_DIR, "crl.pem"), + "-text") + combined = r.stdout + r.stderr + # If it says "not available", the feature is missing + return "CRL print not available in version of wolfSSL" not in combined + + +def _cleanup(*files): + for f in files: + if os.path.exists(f): + os.remove(f) + + +class TestCRLVerify(unittest.TestCase): + """CRL verification tests.""" + + @classmethod + def setUpClass(cls): + cls.have_crl = _has_crl() + if not cls.have_crl: + raise unittest.SkipTest("CRL not compiled into wolfSSL") + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_crl_print(self): + """CRL output should contain BEGIN marker.""" + r = run_wolfssl("crl", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + "-in", os.path.join(CERTS_DIR, "crl.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("BEGIN", r.stdout) + + def test_crl_noout(self): + """CRL -noout should not print the CRL PEM.""" + r = run_wolfssl("crl", "-noout", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + "-in", os.path.join(CERTS_DIR, "crl.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertNotIn("BEGIN X509 CRL", r.stdout) + + def test_crl_der_parse_cert_fails(self): + """Parsing a certificate as CRL (DER) should fail.""" + r = run_wolfssl("crl", "-inform", "DER", "-outform", "PEM", + "-in", os.path.join(CERTS_DIR, "ca-cert.der")) + self.assertNotEqual(r.returncode, 0) + + def test_crl_verify_with_wrong_ca(self): + """CRL verification with a non-CA cert should fail.""" + client_cert = "test_crl_client.pem" + self._clean(client_cert) + r = run_wolfssl("req", "-new", "-days", "3650", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "/O=wolfSSL/C=US/ST=WA/L=Seattle/CN=wolfSSL/OU=org-unit", + "-out", client_cert, "-x509") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("crl", "-noout", "-CAfile", client_cert, + "-in", os.path.join(CERTS_DIR, "crl.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_crl_missing_cafile_fails(self): + """CRL with nonexistent CAfile should fail.""" + r = run_wolfssl("crl", "-noout", "-CAfile", + os.path.join(CERTS_DIR, "ca-cer.pem"), + "-in", os.path.join(CERTS_DIR, "crl.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_crl_missing_input_fails(self): + """CRL with nonexistent input file should fail.""" + r = run_wolfssl("crl", "-noout", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + "-in", os.path.join(CERTS_DIR, "cl.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_crl_verify_wrong_issuer_fails(self): + """CRL verification with wrong issuer cert should fail.""" + r = run_wolfssl("crl", "-noout", "-CAfile", + os.path.join(CERTS_DIR, "client-int-cert.pem"), + "-in", os.path.join(CERTS_DIR, "crl.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_crl_der_to_pem(self): + """CRL DER -> PEM conversion and verification.""" + out_pem = "test_crl_d2p.pem" + self._clean(out_pem) + + r = run_wolfssl("crl", "-inform", "DER", "-outform", "PEM", + "-in", os.path.join(CERTS_DIR, "crl.der"), + "-out", out_pem) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("crl", "-noout", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + "-in", out_pem) + self.assertEqual(r2.returncode, 0, r2.stderr) + + def test_crl_der_cert_to_pem_fails(self): + """Converting a cert DER as CRL should fail.""" + out = "test_crl_bad.pem" + self._clean(out) + r = run_wolfssl("crl", "-inform", "DER", "-outform", "PEM", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-out", out) + self.assertNotEqual(r.returncode, 0) + + def test_crl_fail_no_output_file(self): + """Failed CRL conversion should not create output file.""" + out = "test_crl_nofile.pem" + _cleanup(out) # ensure clean before test + self._clean(out) + r = run_wolfssl("crl", "-inform", "DER", "-outform", "PEM", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-out", out) + self.assertNotEqual(r.returncode, 0) + self.assertFalse(os.path.isfile(out), + "output file should not be created on failure") + + +class TestCRLText(unittest.TestCase): + """CRL -text output tests.""" + + @classmethod + def setUpClass(cls): + if not _has_crl(): + raise unittest.SkipTest("CRL not compiled into wolfSSL") + if not _has_crl_text(): + raise unittest.SkipTest("CRL -text not available in this wolfSSL") + + def test_crl_text_noout(self): + """CRL -text -noout should show CRL info.""" + r = run_wolfssl("crl", "-noout", + "-in", os.path.join(CERTS_DIR, "crl.pem"), + "-text") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("Certificate Revocation List (CRL):", r.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/x509/CRL-verify-test.sh b/tests/x509/CRL-verify-test.sh deleted file mode 100755 index 270e3c44..00000000 --- a/tests/x509/CRL-verify-test.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -run_success() { - RESULT=`./wolfssl $1` - if [ $? != 0 ]; then - echo "Fail on ./wolfssl $1" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - if [ $? == 0 ]; then - echo "Fail on ./wolfssl $1" - exit 99 - fi -} - - -# Test if CRL compiled in -RESULT=`./wolfssl crl -CAfile ./certs/ca-cert.pem -in ./certs/crl.pem 2>&1` -echo $RESULT | grep "recompile wolfSSL with CRL support" -if [ $? == 0 ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - - -# check that the CRL was printed out -run_success "crl -CAfile ./certs/ca-cert.pem -in ./certs/crl.pem" -echo $RESULT | grep BEGIN -if [ $? != 0 ]; then - echo "CRL not printed when should have been" - exit 99 -fi - - -# check that the CRL was not printed out -run_success "crl -noout -CAfile ./certs/ca-cert.pem -in ./certs/crl.pem" -echo $RESULT | grep "BEGIN X509 CRL" -if [ $? == 0 ]; then - echo "CRL printed when should not have been" - exit 99 -fi - -# check that 1 is returned on fail to parse CRL -run_fail "crl -inform DER -outform PEM -in ./certs/ca-cert.der" - -run_success "req -new -days 3650 -key ./certs/server-key.pem -subj /O=wolfSSL/C=US/ST=WA/L=Seattle/CN=wolfSSL/OU=org-unit -out client.pem -x509" -run_fail "crl -noout -CAfile client.pem -in ./certs/crl.pem" -rm -rf client.pem - -# fail to load -run_fail "crl -noout -CAfile ./certs/ca-cer.pem -in ./certs/crl.pem" -run_fail "crl -noout -CAfile ./certs/ca-cert.pem -in ./certs/cl.pem" - -# fail to verify -run_fail "crl -noout -CAfile ./certs/client-cert.pem -in ./certs/crl.pem" - -run_success "crl -inform DER -outform PEM -in ./certs/crl.der -out ./test-crl.pem" -run_success "crl -noout -CAfile ./certs/ca-cert.pem -in ./test-crl.pem" -run_fail "crl -inform DER -outform PEM -in ./certs/ca-cert.der -out test.crl.pem" -rm -f test-crl.pem - -rm -f test.crl.pem -run_fail "crl -inform DER -outform PEM -in ./certs/ca-cert.der -out test.crl.pem" -if [ -f "test.crl.pem" ]; then - echo "file test.crl.pem should not have been created on fail case" - exit 99 -fi - -RESULT=`./wolfssl crl -in certs/crl.pem -text` -echo $RESULT | grep "CRL print not available in version of wolfSSL" -if [ $? == 0 ]; then - # check the CRL -text arg - run_success "crl -noout -in ./certs/crl.pem -text" - echo $RESULT | grep "Certificate Revocation List (CRL):" - if [ $? != 0 ]; then - echo $RESULT - echo "Couldn't find expected output" - exit 99 - fi -fi - -echo "Done" -exit 0 - diff --git a/tests/x509/include.am b/tests/x509/include.am index 89cef8cf..46cd3a28 100644 --- a/tests/x509/include.am +++ b/tests/x509/include.am @@ -2,9 +2,9 @@ # included from top level Makefile.am # ALl path should be given relative to root directory -dist_noinst_SCRIPTS+=tests/x509/x509-ca-test.sh -dist_noinst_SCRIPTS+=tests/x509/x509-process-test.sh -dist_noinst_SCRIPTS+=tests/x509/x509-req-test.sh -dist_noinst_SCRIPTS+=tests/x509/x509-verify-test.sh -dist_noinst_SCRIPTS+=tests/x509/CRL-verify-test.sh +dist_noinst_SCRIPTS+=tests/x509/x509-ca-test.py +dist_noinst_SCRIPTS+=tests/x509/x509-process-test.py +dist_noinst_SCRIPTS+=tests/x509/x509-req-test.py +dist_noinst_SCRIPTS+=tests/x509/x509-verify-test.py +dist_noinst_SCRIPTS+=tests/x509/CRL-verify-test.py diff --git a/tests/x509/x509-ca-test.py b/tests/x509/x509-ca-test.py new file mode 100644 index 00000000..b06abe7f --- /dev/null +++ b/tests/x509/x509-ca-test.py @@ -0,0 +1,751 @@ +#!/usr/bin/env python3 +"""Tests for wolfssl ca (converted from x509-ca-test.sh).""" + +import os +import shutil +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl + +_SKIP_WIN = sys.platform == "win32" +_WIN_REASON = "CA config file paths not supported on Windows UNC shares" + +CA_CONF = """\ +[ ca ] +default_ca = CA_default + +[ usr_cert ] + +basicConstraints=CA:FALSE +nsComment = "wolfSSL Generated Certificate using wolfSSL command line utility." +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +[ CA_default ] + +dir = ./certs +database = ./index.txt +new_certs_dir = ./ + +certificate = $dir/ca-cert.pem +private_key = $dir/ca-key.pem +rand_serial = yes + +default_days = 365 +default_md = sha256 + +policy = policy_any + +[ policy_any] +countryName = supplied +stateOrProvinceName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional +""" + +CA_2_CONF = """\ +[ ca ] +default_ca = CA_default + +[ usr_cert ] + +basicConstraints=CA:FALSE +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +[ CA_default ] + +dir = ./certs +database = ./index.txt +new_certs_dir = ./ +certificate = $dir/ca-cert.pem +private_key = $dir/ca-key.pem +RANDFILE = ./rand-file-test +serial = ./serial-file-test +default_days = 365 +default_md = sha256 +unique_subject = yes + +policy = policy_any + +[ policy_any] +countryName = supplied +stateOrProvinceName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional +""" + +CA_MATCH_CONF = """\ +[ ca ] +default_ca = CA_default + +[ usr_cert ] + +basicConstraints=CA:FALSE + +[ CA_default ] + +dir = ./certs +database = ./index.txt +new_certs_dir = ./ + +certificate = $dir/ca-cert.pem +private_key = $dir/ca-key.pem +rand_serial = yes + +default_days = 365 +default_md = sha256 + +policy = policy_match + +[ policy_match ] +countryName = supplied +stateOrProvinceName = optional +organizationName = match +organizationalUnitName = optional +commonName = match +emailAddress = optional + +crl_dir = ./crls-test +crlnumber = ./crlnumber-test +crl = ./certs/crl.pem +""" + +CA_CRL_CONF = """\ +[ ca ] +default_ca = CA_default + +[ usr_cert ] + +basicConstraints=CA:FALSE + +[ CA_default ] + +dir = ./certs +database = ./index.txt +new_certs_dir = ./ + +certificate = $dir/ca-cert.pem +private_key = $dir/ca-key.pem +rand_serial = yes + +default_days = 365 +default_md = sha256 + +policy = policy_any + +[ policy_any] +countryName = supplied +stateOrProvinceName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +crl_dir = ./crls-test +crlnumber = ./crlnumber-test +crl = ./certs/crl.pem +""" + +CA_OUTDIR_CONF_TEMPLATE = """\ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./certs +database = ./index.txt +new_certs_dir = {new_certs_dir} +certificate = $dir/ca-cert.pem +private_key = $dir/ca-key.pem +rand_serial = yes +default_days = 365 +default_md = sha256 +policy = policy_any + +[ policy_any ] +countryName = supplied +stateOrProvinceName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional +""" + + +def _cleanup(*files): + for f in files: + if os.path.isdir(f): + shutil.rmtree(f, ignore_errors=True) + elif os.path.exists(f): + os.remove(f) + + +def _touch(path): + with open(path, "a"): + pass + + +def _has_altextend(): + """Check whether chimera cert (altextend) support is available.""" + r = run_wolfssl("ca", "-help") + combined = r.stdout + r.stderr + return "altextend" in combined + + +class TestCAHelp(unittest.TestCase): + """ca -h and -help should succeed.""" + + def test_ca_h(self): + r = run_wolfssl("ca", "-h") + self.assertEqual(r.returncode, 0, r.stderr) + + def test_ca_help(self): + r = run_wolfssl("ca", "-help") + self.assertEqual(r.returncode, 0, r.stderr) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestCASelfSign(unittest.TestCase): + """ca -selfsign tests.""" + + @classmethod + def setUpClass(cls): + cls.conf = "ca_selfsign.conf" + with open(cls.conf, "w") as f: + f.write(CA_CONF) + _touch("index.txt") + cls.csr = "ca_selfsign.csr" + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "/O=wolfSSL/C=US/ST=MT/L=Bozeman/CN=wolfSSL/OU=org-unit", + "-out", cls.csr) + assert r.returncode == 0, "CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf, cls.csr, "index.txt") + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_bad_config_fails(self): + """Reading nonexistent config file should fail.""" + r = run_wolfssl("ca", "-config", "ca-example.conf", + "-in", self.csr, "-out", "tmp_bad.pem", + "-md", "sha256", "-selfsign", + "-keyfile", os.path.join(CERTS_DIR, "ca-key.pem")) + self._clean("tmp_bad.pem") + self.assertNotEqual(r.returncode, 0) + + def test_selfsign_key_mismatch_fails(self): + """selfsign with wrong key (ca-key vs server-key CSR) should fail.""" + out = "tmp_selfsign_mm.pem" + self._clean(out) + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out, + "-md", "sha256", "-selfsign", + "-keyfile", os.path.join(CERTS_DIR, "ca-key.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_selfsign_correct_key(self): + """selfsign with matching key succeeds, subject == issuer.""" + out = "test_ca_selfsign.pem" + self._clean(out) + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out, + "-md", "sha256", "-selfsign", + "-keyfile", + os.path.join(CERTS_DIR, "server-key.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + subj = run_wolfssl("x509", "-in", out, "-subject", "-noout") + issu = run_wolfssl("x509", "-in", out, "-issuer", "-noout") + self.assertEqual(subj.stdout.strip(), issu.stdout.strip(), + "subject and issuer mismatch on self-signed cert") + + def test_selfsign_verify_fails_wrong_ca(self): + """Self-signed cert should not verify with unrelated CAs.""" + out = "test_ca_selfsign_vf.pem" + self._clean(out) + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out, + "-md", "sha256", "-selfsign", + "-keyfile", + os.path.join(CERTS_DIR, "server-key.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + r1 = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "server-cert.pem"), out) + self.assertNotEqual(r1.returncode, 0) + + r2 = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), out) + self.assertNotEqual(r2.returncode, 0) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestCACreateAndVerify(unittest.TestCase): + """ca certificate creation and verification.""" + + @classmethod + def setUpClass(cls): + cls.conf = "ca_create.conf" + with open(cls.conf, "w") as f: + f.write(CA_CONF) + _cleanup("index.txt") + _touch("index.txt") + cls.csr = "ca_create.csr" + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "/O=wolfSSL/C=US/ST=MT/L=Bozeman/CN=wolfSSL/OU=org-unit", + "-out", cls.csr) + assert r.returncode == 0, "CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf, cls.csr, "index.txt") + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_create_and_verify(self): + """Create cert and verify with CA.""" + out = "test_ca_cv.pem" + self._clean(out) + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), out) + self.assertEqual(r2.returncode, 0, r2.stderr) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestCAOverrideConfig(unittest.TestCase): + """Override config options with command-line flags.""" + + @classmethod + def setUpClass(cls): + cls.conf = "ca_override.conf" + with open(cls.conf, "w") as f: + f.write(CA_CONF) + _cleanup("index.txt") + _touch("index.txt") + cls.csr = "ca_override.csr" + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "/O=wolfSSL/C=US/ST=MT/L=Bozeman/CN=wolfSSL/OU=org-unit", + "-out", cls.csr) + assert r.returncode == 0, "CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf, cls.csr, "index.txt") + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_override_extensions_md_days_cert_keyfile(self): + """Override -extensions, -md, -days, -cert, -keyfile.""" + out = "test_ca_override.pem" + self._clean(out) + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out, + "-extensions", "usr_cert", + "-md", "sha512", + "-days", "3650", + "-cert", + os.path.join(CERTS_DIR, "ca-ecc-cert.pem"), + "-keyfile", + os.path.join(CERTS_DIR, "ca-ecc-key.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestCAKeyMismatch(unittest.TestCase): + """ca with mismatched key should fail.""" + + @classmethod + def setUpClass(cls): + cls.conf = "ca_keymm.conf" + with open(cls.conf, "w") as f: + f.write(CA_CONF) + _cleanup("index.txt") + _touch("index.txt") + cls.csr = "ca_keymm.csr" + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "/O=wolfSSL/C=US/ST=MT/L=Bozeman/CN=wolfSSL/OU=org-unit", + "-out", cls.csr) + assert r.returncode == 0, "CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf, cls.csr, "index.txt") + + def test_key_mismatch(self): + out = "test_ca_km.pem" + self.addCleanup(lambda: _cleanup(out)) + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out, + "-keyfile", + os.path.join(CERTS_DIR, "ecc-key.pem")) + self.assertNotEqual(r.returncode, 0) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestCAUniqueSubjectAndSerial(unittest.TestCase): + """unique_subject enforcement and serial number handling.""" + + def setUp(self): + self.conf = "ca_uniq.conf" + with open(self.conf, "w") as f: + f.write(CA_2_CONF) + _cleanup("index.txt", "serial-file-test", "rand-file-test") + _touch("index.txt") + with open("serial-file-test", "w") as f: + f.write("01\n") + _touch("rand-file-test") + self.csr = "ca_uniq.csr" + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "/O=wolfSSL/C=US/ST=MT/L=Bozeman/CN=wolfSSL/OU=org-unit", + "-out", self.csr) + assert r.returncode == 0, "CSR creation failed: " + r.stderr + + def tearDown(self): + _cleanup(self.conf, self.csr, "index.txt", + "serial-file-test", "rand-file-test") + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_unique_subject_fail(self): + """Creating same subject twice with unique_subject=yes should fail.""" + out = "test_ca_uniq.pem" + self._clean(out) + # First cert succeeds + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + # Second with same subject should fail + r2 = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out) + self.assertNotEqual(r2.returncode, 0) + + def test_serial_number(self): + """First cert should have serial=01.""" + out = "test_ca_serial.pem" + self._clean(out) + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("x509", "-in", out, "-noout", "-serial") + self.assertEqual(r2.returncode, 0, r2.stderr) + self.assertEqual(r2.stdout.strip(), "serial=01") + + def test_serial_increment(self): + """Second cert should have serial=02.""" + out1 = "test_ca_serial1.pem" + out2 = "test_ca_serial2.pem" + self._clean(out1, out2) + + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out1) + self.assertEqual(r.returncode, 0, r.stderr) + + # Reset index for unique_subject + _cleanup("index.txt") + _touch("index.txt") + + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out2) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("x509", "-in", out2, "-noout", "-serial") + self.assertEqual(r2.returncode, 0, r2.stderr) + self.assertEqual(r2.stdout.strip(), "serial=02") + + def test_rand_file_written(self): + """Rand file should be 256 bytes after cert creation.""" + out = "test_ca_rand.pem" + self._clean(out) + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(os.path.isfile("rand-file-test")) + size = os.path.getsize("rand-file-test") + self.assertEqual(size, 256, + "rand file is {} bytes, expected 256".format(size)) + + def test_rand_file_changes(self): + """Rand file should change between cert creations.""" + out1 = "test_ca_randc1.pem" + out2 = "test_ca_randc2.pem" + self._clean(out1, out2) + + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out1) + self.assertEqual(r.returncode, 0, r.stderr) + with open("rand-file-test", "rb") as f: + rand1 = f.read() + + _cleanup("index.txt") + _touch("index.txt") + + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out2) + self.assertEqual(r.returncode, 0, r.stderr) + with open("rand-file-test", "rb") as f: + rand2 = f.read() + + self.assertNotEqual(rand1, rand2, "rand file did not change") + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestCAPolicy(unittest.TestCase): + """Policy section enforcement.""" + + @classmethod + def setUpClass(cls): + cls.conf = "ca_policy.conf" + cls.match_conf = "ca_policy_match.conf" + with open(cls.conf, "w") as f: + f.write(CA_CONF) + with open(cls.match_conf, "w") as f: + f.write(CA_MATCH_CONF) + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf, cls.match_conf) + + def setUp(self): + _cleanup("index.txt") + _touch("index.txt") + + def tearDown(self): + _cleanup("index.txt") + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_no_common_name_supplied_fails(self): + """CSR without commonName should fail when policy requires 'supplied'.""" + csr = "ca_pol_nocn.csr" + out = "ca_pol_nocn.pem" + self._clean(csr, out) + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", "O=wolfSSL/C=US/ST=MT/L=Bozeman/OU=org-unit", + "-out", csr) + self.assertEqual(r.returncode, 0, r.stderr) + r = run_wolfssl("ca", "-config", self.conf, + "-in", csr, "-out", out, + "-md", "sha256", + "-keyfile", os.path.join(CERTS_DIR, "ca-key.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_no_common_name_match_fails(self): + """CSR without commonName should also fail with match policy.""" + csr = "ca_pol_nocnm.csr" + out = "ca_pol_nocnm.pem" + self._clean(csr, out) + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", "O=wolfSSL/C=US/ST=MT/L=Bozeman/OU=org-unit", + "-out", csr) + self.assertEqual(r.returncode, 0, r.stderr) + r = run_wolfssl("ca", "-config", self.match_conf, + "-in", csr, "-out", out, + "-md", "sha256", + "-keyfile", os.path.join(CERTS_DIR, "ca-key.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_common_name_supplied_succeeds(self): + """CSR with commonName should pass policy_any.""" + csr = "ca_pol_cn.csr" + out = "ca_pol_cn.pem" + self._clean(csr, out) + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "O=Sawtooth/CN=www.wolfclu.com/C=US/ST=MT/L=Bozeman/OU=org-unit", + "-out", csr) + self.assertEqual(r.returncode, 0, r.stderr) + r = run_wolfssl("ca", "-config", self.conf, + "-in", csr, "-out", out, + "-md", "sha256", + "-keyfile", os.path.join(CERTS_DIR, "ca-key.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_common_name_mismatch_fails(self): + """CSR with non-matching commonName should fail policy_match.""" + csr = "ca_pol_cnmm.csr" + out = "ca_pol_cnmm.pem" + self._clean(csr, out) + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "O=Sawtooth/CN=www.wolfclu.com/C=US/ST=MT/L=Bozeman/OU=org-unit", + "-out", csr) + self.assertEqual(r.returncode, 0, r.stderr) + r = run_wolfssl("ca", "-config", self.match_conf, + "-in", csr, "-out", out, + "-md", "sha256", + "-keyfile", os.path.join(CERTS_DIR, "ca-key.pem")) + self.assertNotEqual(r.returncode, 0) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestCAChimera(unittest.TestCase): + """Chimera certificate (altextend) tests.""" + + @classmethod + def setUpClass(cls): + if not _has_altextend(): + raise unittest.SkipTest("altextend not available") + cls.conf = "ca_chimera.conf" + with open(cls.conf, "w") as f: + f.write(CA_CONF) + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf) + + def setUp(self): + _cleanup("index.txt") + _touch("index.txt") + + def tearDown(self): + _cleanup("index.txt") + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_chimera_cert(self): + """Create chimera (alt-extended) certificate chain.""" + ca_cert = "tmp_chimera_ca.pem" + ca_chimera = "tmp_chimera_ca_chimera.pem" + server_csr = "tmp_chimera_server.csr" + server_cert = "tmp_chimera_server.pem" + server_chimera = "tmp_chimera_server_chimera.pem" + self._clean(ca_cert, ca_chimera, server_csr, server_cert, + server_chimera) + + r = run_wolfssl("req", "-new", "-x509", + "-key", os.path.join(CERTS_DIR, "ca-ecc-key.pem"), + "-subj", + "O=org-A/C=US/ST=WA/L=Seattle/CN=A/OU=org-unit-A", + "-out", ca_cert, "-outform", "PEM") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("ca", "-altextend", "-in", ca_cert, + "-keyfile", os.path.join(CERTS_DIR, "ca-ecc-key.pem"), + "-altkey", + os.path.join(CERTS_DIR, "ca-mldsa44-key.pem"), + "-altpub", + os.path.join(CERTS_DIR, "ca-mldsa44-keyPub.pem"), + "-out", ca_chimera) + self.assertEqual(r.returncode, 0, r.stderr) + + _cleanup("index.txt") + _touch("index.txt") + + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-ecc-key.pem"), + "-subj", + "O=org-B/C=US/ST=WA/L=Seattle/CN=B/OU=org-unit-B", + "-out", server_csr, "-outform", "PEM") + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("ca", "-in", server_csr, + "-keyfile", os.path.join(CERTS_DIR, "ca-ecc-key.pem"), + "-cert", ca_cert, "-out", server_cert) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl( + "ca", "-altextend", "-in", server_cert, + "-keyfile", os.path.join(CERTS_DIR, "ca-ecc-key.pem"), + "-altkey", os.path.join(CERTS_DIR, "ca-mldsa44-key.pem"), + "-altpub", + os.path.join(CERTS_DIR, "server-mldsa44-keyPub.pem"), + "-subjkey", os.path.join(CERTS_DIR, "server-ecc-key.pem"), + "-cert", ca_chimera, "-out", server_chimera) + self.assertEqual(r.returncode, 0, r.stderr) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestCAOutdirPath(unittest.TestCase): + """Test path concatenation for -out with new_certs_dir.""" + + def setUp(self): + self.outdir = "outdir-test" + self.outdir_certs = os.path.join(self.outdir, "certs") + os.makedirs(self.outdir_certs, exist_ok=True) + self.conf = "ca_outdir.conf" + with open(self.conf, "w") as f: + f.write(CA_OUTDIR_CONF_TEMPLATE.format( + new_certs_dir="./" + os.path.join(self.outdir, "certs"))) + _cleanup("index.txt") + _touch("index.txt") + self.csr = "ca_outdir.csr" + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "/O=wolfSSL/C=US/ST=MT/L=Bozeman/CN=wolfSSL/OU=org-unit", + "-out", self.csr) + assert r.returncode == 0, "CSR creation failed: " + r.stderr + + def tearDown(self): + _cleanup(self.conf, self.csr, "index.txt", self.outdir) + + def test_absolute_out_path(self): + """Absolute -out path should override new_certs_dir.""" + abs_out = os.path.abspath( + os.path.join(self.outdir, "absolute-out.pem")) + self.addCleanup(lambda: _cleanup(abs_out)) + + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", abs_out) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(os.path.isfile(abs_out), + "File not found at {}".format(abs_out)) + + # The file at the absolute location is the correct one; + # just verify it was created (already checked above). + + def test_relative_out_path(self): + """Relative -out path should be appended to new_certs_dir.""" + _cleanup("index.txt") + _touch("index.txt") + + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", "relative-out.pem") + self.assertEqual(r.returncode, 0, r.stderr) + + expected = os.path.join(self.outdir_certs, "relative-out.pem") + self.assertTrue(os.path.isfile(expected), + "File not found at {}".format(expected)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/x509/x509-ca-test.sh b/tests/x509/x509-ca-test.sh deleted file mode 100755 index 2da21f32..00000000 --- a/tests/x509/x509-ca-test.sh +++ /dev/null @@ -1,354 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -run_success() { - RESULT=`./wolfssl $1` - if [ $? != 0 ]; then - echo "Fail on ./wolfssl $1" - exit 99 - fi -} - -run_fail() { - RESULT=`./wolfssl $1` - if [ $? == 0 ]; then - echo "Fail on ./wolfssl $1" - exit 99 - fi -} - - -cat << EOF >> ca.conf -[ ca ] -default_ca = CA_default - -[ usr_cert ] - -basicConstraints=CA:FALSE -nsComment = "wolfSSL Generated Certificate using wolfSSL command line utility." -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid,issuer - -[ CA_default ] - -dir = ./certs -database = ./index.txt -new_certs_dir = ./ - -certificate = \$dir/ca-cert.pem -private_key = \$dir/ca-key.pem -rand_serial = yes - -default_days = 365 -default_md = sha256 - -policy = policy_any - -[ policy_any] -countryName = supplied -stateOrProvinceName = optional -organizationName = optional -organizationalUnitName = optional -commonName = supplied -emailAddress = optional -EOF - -cat << EOF >> ca-2.conf -[ ca ] -default_ca = CA_default - -[ usr_cert ] - -basicConstraints=CA:FALSE -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid,issuer - -[ CA_default ] - -dir = ./certs -database = ./index.txt -new_certs_dir = ./ -certificate = \$dir/ca-cert.pem -private_key = \$dir/ca-key.pem -RANDFILE = ./rand-file-test -serial = ./serial-file-test -default_days = 365 -default_md = sha256 -unique_subject = yes - -policy = policy_any - -[ policy_any] -countryName = supplied -stateOrProvinceName = optional -organizationName = optional -organizationalUnitName = optional -commonName = supplied -emailAddress = optional -EOF - -cat << EOF >> ca-match.conf -[ ca ] -default_ca = CA_default - -[ usr_cert ] - -basicConstraints=CA:FALSE - -[ CA_default ] - -dir = ./certs -database = ./index.txt -new_certs_dir = ./ - -certificate = \$dir/ca-cert.pem -private_key = \$dir/ca-key.pem -rand_serial = yes - -default_days = 365 -default_md = sha256 - -policy = policy_match - -[ policy_match ] -countryName = supplied -stateOrProvinceName = optional -organizationName = match -organizationalUnitName = optional -commonName = match -emailAddress = optional - -crl_dir = ./crls-test -crlnumber = ./crlnumber-test -crl = ./certs/crl.pem -EOF - -touch index.txt -cat << EOF >> ca-crl.conf -[ ca ] -default_ca = CA_default - -[ usr_cert ] - -basicConstraints=CA:FALSE - -[ CA_default ] - -dir = ./certs -database = ./index.txt -new_certs_dir = ./ - -certificate = \$dir/ca-cert.pem -private_key = \$dir/ca-key.pem -rand_serial = yes - -default_days = 365 -default_md = sha256 - -policy = policy_any - -[ policy_any] -countryName = supplied -stateOrProvinceName = optional -organizationName = optional -organizationalUnitName = optional -commonName = supplied -emailAddress = optional - -crl_dir = ./crls-test -crlnumber = ./crlnumber-test -crl = ./certs/crl.pem -EOF - -touch index.txt -run_success "ca -h" -run_success "ca -help" -run_success "req -key ./certs/server-key.pem -subj /O=wolfSSL/C=US/ST=MT/L=Bozeman/CN=wolfSSL/OU=org-unit -out tmp-ca.csr" - -# testing reading bad conf file -run_fail "ca -config ca-example.conf -in tmp-ca.csr -out tmp.pem -md sha256 -selfsign -keyfile ./certs/ca-key.pem" - -# testing out selfsign -run_fail "ca -config ca.conf -in tmp-ca.csr -out tmp.pem -md sha256 -selfsign -keyfile ./certs/ca-key.pem" -run_success "ca -config ca.conf -in tmp-ca.csr -out test_ca.pem -md sha256 -selfsign -keyfile ./certs/server-key.pem" -SUBJ=`./wolfssl x509 -in test_ca.pem -subject -noout` -ISSU=`./wolfssl x509 -in test_ca.pem -issuer -noout` -if [ "$SUBJ" != "$ISSU" ]; then - echo "subject and issuer missmatch on self signed cert" - exit 99 -fi -run_fail "verify -CAfile ./certs/server-cert.pem test_ca.pem" -run_fail "verify -CAfile ./certs/ca-cert.pem test_ca.pem" - -# create a certificate and then verify it -run_success "ca -config ca.conf -in tmp-ca.csr -out test_ca.pem" -run_success "verify -CAfile ./certs/ca-cert.pem test_ca.pem" - -# override almost all info from config file -run_success "ca -config ca.conf -in tmp-ca.csr -out test_ca.pem -extensions usr_cert -md sha512 -days 3650 -cert ./certs/ca-ecc-cert.pem -keyfile ./certs/ca-ecc-key.pem" - -# test key missmatch -run_fail "ca -config ca.conf -in tmp-ca.csr -out test_ca.pem -keyfile ./certs/ecc-key.pem" - -# hit unique subject fail case -rm -f serial-file-test -echo "01" > serial-file-test -touch rand-file-test -run_fail "ca -config ca-2.conf -in tmp-ca.csr -out test_ca.pem" -rm index.txt -touch index.txt -run_success "ca -config ca-2.conf -in tmp-ca.csr -out test_ca.pem" -run_success "x509 -in test_ca.pem -noout -serial" -if [ "$RESULT" != "serial=01" ]; then - echo "Unexpected serial number!" - exit 99 -fi - -# test rand file -wc -c rand-file-test | grep 256 -if [ $? != 0 ]; then - echo "rand file was not 256 bytes" - exit 99 -fi -RAND=`cat rand-file-test` - -# test increment of serial number -rm index.txt -touch index.txt -run_success "ca -config ca-2.conf -in tmp-ca.csr -out test_ca.pem" -run_success "x509 -in test_ca.pem -noout -serial" -if [ "$RESULT" != "serial=02" ]; then - echo "Unexpected serial number!" - exit 99 -fi - -# test rand file -NEW_RAND=`cat rand-file-test` -if [ "$RAND" == "$NEW_RAND" ]; then - echo "rand file was the same.." - echo $NEW_RAND | base64 - echo $RAND | base64 - exit 99 -fi - -# testing on policy section in conf file -echo "Testing policy section" - -# no common name when is 'supplied' -run_success "req -key ./certs/server-key.pem -subj O=wolfSSL/C=US/ST=MT/L=Bozeman/OU=org-unit -out tmp-ca.csr" -echo "Test fail when common name not supplied" -run_fail "ca -config ca.conf -in tmp-ca.csr -out tmp.pem -md sha256 -keyfile ./certs/ca-key.pem" -echo "Test fail when common name not supplied 'ca-match.conf'" -run_fail "ca -config ca-match.conf -in tmp-ca.csr -out tmp.pem -md sha256 -keyfile ./certs/ca-key.pem" -echo "Test success when common name supplied" -run_success "req -key ./certs/server-key.pem -subj O=Sawtooth/CN=www.wolfclu.com/C=US/ST=MT/L=Bozeman/OU=org-unit -out tmp-ca.csr" -run_success "ca -config ca.conf -in tmp-ca.csr -out tmp.pem -md sha256 -keyfile ./certs/ca-key.pem" - -# common name mismatch -run_success "req -key ./certs/server-key.pem -subj O=Sawtooth/CN=www.wolfclu.com/C=US/ST=MT/L=Bozeman/OU=org-unit -out tmp-ca.csr" -run_fail "ca -config ca-match.conf -in tmp-ca.csr -out tmp.pem -md sha256 -keyfile ./certs/ca-key.pem" - -# chimera certificate test -if ./wolfssl ca -help 2>&1 | grep altextend &> /dev/null; then - echo "Testing chimera certificate extension" - run_success "req -new -x509 -key ./certs/ca-ecc-key.pem -subj O=org-A/C=US/ST=WA/L=Seattle/CN=A/OU=org-unit-A -out tmp-ca-cert.pem -outform PEM" - run_success "ca -altextend -in tmp-ca-cert.pem -keyfile ./certs/ca-ecc-key.pem -altkey ./certs/ca-mldsa44-key.pem -altpub ./certs/ca-mldsa44-keyPub.pem -out tmp-ca-chimera-cert.pem" - - run_success "req -new -key ./certs/server-ecc-key.pem -subj O=org-B/C=US/ST=WA/L=Seattle/CN=B/OU=org-unit-B -out tmp-server.csr -outform PEM" - run_success "ca -in tmp-server.csr -keyfile ./certs/ca-ecc-key.pem -cert tmp-ca-cert.pem -out tmp-server-cert.pem" - run_success "ca -altextend -in tmp-server-cert.pem -keyfile ./certs/ca-ecc-key.pem -altkey ./certs/ca-mldsa44-key.pem -altpub ./certs/server-mldsa44-keyPub.pem -subjkey ./certs/server-ecc-key.pem -cert tmp-ca-chimera-cert.pem -out tmp-server-chimera-cert.pem" - - echo "Chimera certificate test passed" - - rm -f tmp-ca-cert.pem - rm -f tmp-ca-chimera-cert.pem - rm -f tmp-server.csr - rm -f tmp-server-cert.pem - rm -f tmp-server-chimera-cert.pem -fi - -# Test path concatenation fix for -out with new_certs_dir -echo "Testing -out path handling with new_certs_dir" -mkdir -p outdir-test/certs -cat << EOF > ca-outdir.conf -[ ca ] -default_ca = CA_default - -[ CA_default ] -dir = ./certs -database = ./index.txt -new_certs_dir = ./outdir-test/certs -certificate = \$dir/ca-cert.pem -private_key = \$dir/ca-key.pem -rand_serial = yes -default_days = 365 -default_md = sha256 -policy = policy_any - -[ policy_any ] -countryName = supplied -stateOrProvinceName = optional -organizationName = optional -organizationalUnitName = optional -commonName = supplied -emailAddress = optional -EOF - -rm index.txt -touch index.txt -run_success "req -key ./certs/server-key.pem -subj /O=wolfSSL/C=US/ST=MT/L=Bozeman/CN=wolfSSL/OU=org-unit -out tmp-outdir.csr" - -# Test 1: absolute -out path should override new_certs_dir -ABS_OUT_PATH="$(pwd)/outdir-test/absolute-out.pem" -run_success "ca -config ca-outdir.conf -in tmp-outdir.csr -out $ABS_OUT_PATH" -if [ ! -f "$ABS_OUT_PATH" ]; then - echo "Absolute -out path test failed: file not found at $ABS_OUT_PATH" - exit 99 -fi -if [ -f ./outdir-test/certs"$ABS_OUT_PATH" ]; then - echo "Absolute -out path test failed: file incorrectly concatenated" - exit 99 -fi -echo "Absolute -out path test passed" - -# Test 2: relative -out path should be appended to new_certs_dir -rm index.txt -touch index.txt -run_success "ca -config ca-outdir.conf -in tmp-outdir.csr -out relative-out.pem" -if [ ! -f ./outdir-test/certs/relative-out.pem ]; then - echo "Relative -out path test failed: file not found at ./outdir-test/certs/relative-out.pem" - exit 99 -fi -echo "Relative -out path test passed" - -rm -rf outdir-test -rm -f ca-outdir.conf -rm -f tmp-outdir.csr - -rm -f test_ca.pem -rm -f tmp.pem -rm -f rand-file-test -rm -f serial-file-test -rm -f tmp-ca.csr -rm -f ca.conf -rm -f ca-2.conf -rm -f ca-crl.conf -rm -f ca-match.conf -rm -f index.txt - -echo "Done" -exit 0 - - - diff --git a/tests/x509/x509-process-test.py b/tests/x509/x509-process-test.py new file mode 100644 index 00000000..01533f8c --- /dev/null +++ b/tests/x509/x509-process-test.py @@ -0,0 +1,508 @@ +#!/usr/bin/env python3 +"""Tests for wolfssl x509 processing (converted from x509-process-test.sh).""" + +import os +import shutil +import subprocess +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl + +TESTS_X509_DIR = os.path.join(".", "tests", "x509") +HAS_OPENSSL = shutil.which("openssl") is not None + + +def _check_cert_signature(cert_path, digest): + """Use OpenSSL to verify the signature on a self-signed certificate. + + Returns True on success, raises AssertionError on failure. + Requires openssl, xxd, and the cert to be self-signed. + """ + if not HAS_OPENSSL: + raise unittest.SkipTest("openssl not available") + + stripped = cert_path + ".stripped.pem" + try: + subprocess.run( + ["openssl", "x509", "-in", cert_path, "-out", stripped, + "-outform", "PEM"], + check=True, capture_output=True, timeout=60) + + # Extract signature hex + r = subprocess.run( + ["openssl", "x509", "-in", stripped, "-text", "-noout", + "-certopt", "ca_default", "-certopt", "no_validity", + "-certopt", "no_serial", "-certopt", "no_subject", + "-certopt", "no_extensions", "-certopt", "no_signame"], + check=True, capture_output=True, text=True, timeout=60) + lines = [] + for line in r.stdout.splitlines(): + if "Signature Algorithm" in line: + continue + if "Signature Value" in line: + continue + stripped_line = line.replace(" ", "").replace(":", "") + if stripped_line: + lines.append(stripped_line) + sig_hex = "".join(lines) + + sig_bin = cert_path + ".sig.bin" + with open(sig_bin, "wb") as f: + f.write(bytes.fromhex(sig_hex)) + + body_bin = cert_path + ".body.bin" + subprocess.run( + ["openssl", "asn1parse", "-in", stripped, "-strparse", "4", + "-out", body_bin, "-noout"], + check=True, capture_output=True, timeout=60) + + pub_pem = cert_path + ".pub.pem" + with open(pub_pem, "w") as pub_f: + subprocess.run( + ["openssl", "x509", "-in", stripped, "-noout", "-pubkey"], + check=True, stdout=pub_f, stderr=subprocess.DEVNULL, + timeout=60) + + r = subprocess.run( + ["openssl", "dgst", "-" + digest, "-verify", pub_pem, + "-signature", sig_bin, body_bin], + capture_output=True, text=True, timeout=60) + assert r.returncode == 0, "Signature verification failed for {}".format(cert_path) + finally: + for f in [stripped, sig_bin, body_bin, pub_pem]: + try: + os.remove(f) + except OSError: + pass + + +def _cleanup(*files): + for f in files: + if os.path.exists(f): + os.remove(f) + + +class TestX509ProcessValid(unittest.TestCase): + """run1: valid PEM/DER format conversions and combined file handling.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_1a_pem_to_pem(self): + """PEM -> PEM conversion produces valid output file.""" + out = "test_1a.pem" + self._clean(out) + r = run_wolfssl("x509", "-inform", "pem", "-outform", "pem", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(os.path.isfile(out), "output file not created") + + @unittest.skipUnless(HAS_OPENSSL, "openssl not available") + def test_1a_pem_to_pem_signature(self): + out = "test_1a_sig.pem" + self._clean(out) + r = run_wolfssl("x509", "-inform", "pem", "-outform", "pem", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + _check_cert_signature(out, "sha256") + + def test_1b_pem_text_noout_matches(self): + """PEM text/noout output is identical for original and round-tripped cert.""" + out1 = "test_1b_out.pem" + out2 = "test_1b_ca.pem" + tmp = "test_1b_tmp.pem" + self._clean(out1, out2, tmp) + + r = run_wolfssl("x509", "-inform", "pem", "-outform", "pem", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-out", tmp) + self.assertEqual(r.returncode, 0, r.stderr) + r = run_wolfssl("x509", "-in", tmp, "-text", "-noout", "-out", out1) + self.assertEqual(r.returncode, 0, r.stderr) + r = run_wolfssl("x509", "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-text", "-noout", "-out", out2) + self.assertEqual(r.returncode, 0, r.stderr) + with open(out1) as f: + data1 = f.read() + with open(out2) as f: + data2 = f.read() + self.assertEqual(data1, data2, "PEM text/noout mismatch") + + def test_1c_pem_to_der(self): + """PEM -> DER conversion succeeds.""" + out = "test_1c.der" + self._clean(out) + r = run_wolfssl("x509", "-inform", "pem", "-outform", "der", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + + @unittest.skipUnless(HAS_OPENSSL, "openssl not available") + def test_1c_pem_to_der_signature(self): + out = "test_1c_sig.der" + self._clean(out) + r = run_wolfssl("x509", "-inform", "pem", "-outform", "der", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + _check_cert_signature(out, "sha256") + + def test_1d_der_to_pem_stdout(self): + """DER -> PEM to stdout succeeds.""" + r = run_wolfssl("x509", "-inform", "der", "-outform", "pem", + "-in", os.path.join(CERTS_DIR, "ca-cert.der")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_1e_der_to_der(self): + """DER -> DER conversion succeeds.""" + out = "test_1e.der" + self._clean(out) + r = run_wolfssl("x509", "-inform", "der", "-outform", "der", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + + @unittest.skipUnless(HAS_OPENSSL, "openssl not available") + def test_1e_der_to_der_signature(self): + out = "test_1e_sig.der" + self._clean(out) + r = run_wolfssl("x509", "-inform", "der", "-outform", "der", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + _check_cert_signature(out, "sha256") + + def test_1f_der_text_noout(self): + """DER text/noout succeeds.""" + r = run_wolfssl("x509", "-inform", "der", "-text", "-noout", + "-in", os.path.join(CERTS_DIR, "ca-cert.der")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_1g_der_pubkey_noout(self): + """DER pubkey/noout succeeds.""" + r = run_wolfssl("x509", "-inform", "der", "-pubkey", "-noout", + "-in", os.path.join(CERTS_DIR, "ca-cert.der")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_1h_der_to_pem_file(self): + """DER -> PEM to file succeeds.""" + out = "test_1h.pem" + self._clean(out) + r = run_wolfssl("x509", "-inform", "der", "-outform", "pem", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + + @unittest.skipUnless(HAS_OPENSSL, "openssl not available") + def test_1h_der_to_pem_signature(self): + out = "test_1h_sig.pem" + self._clean(out) + r = run_wolfssl("x509", "-inform", "der", "-outform", "pem", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + _check_cert_signature(out, "sha256") + + def test_1i_combined_pem(self): + """Combined key+cert PEM file is handled correctly.""" + combined = "test_1i_combined.pem" + process_out = "test_1i_process.pem" + ca_out = "test_1i_ca.pem" + self._clean(combined, process_out, ca_out) + + key_path = os.path.join(CERTS_DIR, "ca-key.pem") + cert_path = os.path.join(CERTS_DIR, "ca-cert.pem") + with open(key_path) as kf, open(cert_path) as cf: + with open(combined, "w") as out: + out.write(kf.read()) + out.write(cf.read()) + + r = run_wolfssl("x509", "-in", combined, "-out", process_out) + self.assertEqual(r.returncode, 0, r.stderr) + + r1 = run_wolfssl("x509", "-in", process_out, "-text") + self.assertEqual(r1.returncode, 0, r1.stderr) + + r2 = run_wolfssl("x509", "-in", cert_path, "-text") + self.assertEqual(r2.returncode, 0, r2.stderr) + + self.assertEqual(r1.stdout, r2.stdout, + "combined PEM output differs from original") + + +class TestX509ProcessInvalidInput(unittest.TestCase): + """run2: invalid argument combinations should fail.""" + + def _fail(self, *args): + r = run_wolfssl("x509", *args) + self.assertNotEqual(r.returncode, 0, + "expected failure for: {}".format(args)) + + def test_2a_double_inform(self): + self._fail("-inform", "pem", "-inform", "der") + + def test_2b_double_outform(self): + self._fail("-outform", "pem", "-outform", "der") + + def test_2c_inform_inform(self): + self._fail("-inform", "-inform") + + def test_2d_outform_outform(self): + self._fail("-outform", "-outform") + + def test_2e_triple_inform(self): + self._fail("-inform", "pem", "-inform", "der", "-inform") + + def test_2f_triple_outform(self): + self._fail("-outform", "pem", "-outform", "der", "-outform") + + def test_2g_inform_outform_inform(self): + self._fail("-inform", "pem", "-outform", "der", "-inform") + + def test_2h_outform_inform_outform(self): + self._fail("-outform", "pem", "-inform", "der", "-outform") + + def test_2i_inform_alone(self): + self._fail("-inform") + + def test_2j_outform_alone(self): + self._fail("-outform") + + def test_2k_double_outform_noout(self): + self._fail("-outform", "pem", "-outform", "der", "-noout") + + def test_2l_outform_outform_noout(self): + self._fail("-outform", "-outform", "-noout") + + def test_2m_triple_outform_noout(self): + self._fail("-outform", "pem", "-outform", "der", "-outform", "-noout") + + def test_2n_inform_outform_inform_noout(self): + self._fail("-inform", "pem", "-outform", "der", "-inform", "-noout") + + def test_2o_outform_inform_outform_noout(self): + self._fail("-outform", "pem", "-inform", "der", "-outform", "-noout") + + def test_2p_outform_noout(self): + self._fail("-outform", "-noout") + + +class TestX509ProcessValidFiles(unittest.TestCase): + """run3: valid input file operations and field extraction.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_3a_der_to_pem_matches(self): + """DER -> PEM matches original PEM.""" + test_pem = "test_3a.pem" + tmp_pem = "test_3a_tmp.pem" + self._clean(test_pem, tmp_pem) + + r = run_wolfssl("x509", "-inform", "pem", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-outform", "pem", "-out", test_pem) + self.assertEqual(r.returncode, 0, r.stderr) + r = run_wolfssl("x509", "-inform", "der", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-outform", "pem", "-out", tmp_pem) + self.assertEqual(r.returncode, 0, r.stderr) + with open(test_pem) as f1, open(tmp_pem) as f2: + self.assertEqual(f1.read(), f2.read()) + + def test_3b_pem_to_der_matches(self): + """Two PEM -> DER conversions produce identical output.""" + der1 = "test_3b_1.der" + der2 = "test_3b_2.der" + self._clean(der1, der2) + + r = run_wolfssl("x509", "-inform", "pem", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-outform", "der", "-out", der1) + self.assertEqual(r.returncode, 0, r.stderr) + r = run_wolfssl("x509", "-inform", "pem", "-outform", "der", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-out", der2) + self.assertEqual(r.returncode, 0, r.stderr) + with open(der1, "rb") as f1, open(der2, "rb") as f2: + self.assertEqual(f1.read(), f2.read()) + + def test_3c_subject(self): + expected_file = os.path.join(TESTS_X509_DIR, "expect-subject.txt") + with open(expected_file) as f: + expected = f.read().strip() + r = run_wolfssl("x509", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-subject", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), expected) + + def test_3d_issuer(self): + expected_file = os.path.join(TESTS_X509_DIR, "expect-issuer.txt") + with open(expected_file) as f: + expected = f.read().strip() + r = run_wolfssl("x509", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-issuer", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), expected) + + def test_3e_ca_serial(self): + expected_file = os.path.join(TESTS_X509_DIR, "expect-ca-serial.txt") + with open(expected_file) as f: + expected = f.read().strip() + r = run_wolfssl("x509", "-in", + os.path.join(CERTS_DIR, "ca-cert.pem"), + "-serial", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), expected) + + def test_3f_server_serial(self): + expected_file = os.path.join(TESTS_X509_DIR, + "expect-server-serial.txt") + with open(expected_file) as f: + expected = f.read().strip() + r = run_wolfssl("x509", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-serial", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), expected) + + def test_3g_dates(self): + expected_file = os.path.join(TESTS_X509_DIR, "expect-dates.txt") + with open(expected_file) as f: + expected = f.read().strip() + r = run_wolfssl("x509", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-dates", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), expected) + + def test_3h_email(self): + expected_file = os.path.join(TESTS_X509_DIR, "expect-email.txt") + with open(expected_file) as f: + expected = f.read().strip() + r = run_wolfssl("x509", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-email", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), expected) + + def test_3i_fingerprint(self): + expected_file = os.path.join(TESTS_X509_DIR, + "expect-fingerprint.txt") + with open(expected_file) as f: + expected = f.read().strip() + r = run_wolfssl("x509", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-fingerprint", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + # Strip the prefix "SHA1 of cert. DER : " if present + output = r.stdout.strip() + prefix = "SHA1 of cert. DER : " + if output.startswith(prefix): + output = output[len(prefix):] + self.assertEqual(output, expected) + + def test_3j_purpose(self): + expected_file = os.path.join(TESTS_X509_DIR, "expect-purpose.txt") + with open(expected_file) as f: + expected = f.read().strip() + r = run_wolfssl("x509", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-purpose", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout.strip(), expected) + + def test_3k_hash(self): + expected_file = os.path.join(TESTS_X509_DIR, "expect-hash.txt") + with open(expected_file) as f: + expected = f.read().strip() + old_expected = "f6cf410e" + r = run_wolfssl("x509", "-in", + os.path.join(CERTS_DIR, "server-cert.pem"), + "-hash", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + output = r.stdout.strip() + self.assertTrue(output == expected or output == old_expected, + "hash {} does not match expected {} or {}".format( + output, expected, old_expected)) + + def test_3l_email_from_generated_cert(self): + """Email from a generated self-signed cert (no email) should succeed.""" + tmp_cert = "test_3l.cert" + self._clean(tmp_cert) + r = run_wolfssl("req", "-new", "-days", "3650", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "/O=wolfSSL/C=US/ST=WA/L=Seattle/CN=wolfSSL/OU=org-unit", + "-out", tmp_cert, "-x509") + self.assertEqual(r.returncode, 0, r.stderr) + r = run_wolfssl("x509", "-in", tmp_cert, "-email", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + + +class TestX509ProcessInvalidFiles(unittest.TestCase): + """run4: invalid input files should fail.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_4a_double_in(self): + r = run_wolfssl("x509", "-inform", "der", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-outform", "pem", "-out", "tmp_4a.pem") + self._clean("tmp_4a.pem") + self.assertNotEqual(r.returncode, 0) + + def test_4b_double_out(self): + r = run_wolfssl("x509", "-inform", "der", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-outform", "pem", "-out", "tmp_4b.pem", + "-out", "tmp_4b.pem") + self._clean("tmp_4b.pem") + self.assertNotEqual(r.returncode, 0) + + def test_4c_double_out_double_in(self): + r = run_wolfssl("x509", "-inform", "pem", "-outform", "der", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), + "-out", "tmp_4c.der", "-out", "tmp_4c.der", + "-in", os.path.join(CERTS_DIR, "ca-cert.pem")) + self._clean("tmp_4c.der") + self.assertNotEqual(r.returncode, 0) + + def test_4d_pem_inform_with_der_file(self): + """PEM inform with DER file should fail and not create output.""" + out = "test_4d.der" + self._clean(out) + _cleanup(out) # ensure it doesn't exist before test + r = run_wolfssl("x509", "-inform", "pem", + "-in", os.path.join(CERTS_DIR, "ca-cert.der"), + "-outform", "der", "-out", out) + self.assertNotEqual(r.returncode, 0) + self.assertFalse(os.path.isfile(out), + "output file should not be created on error") + + def test_4e_nonexistent_file_der(self): + r = run_wolfssl("x509", "-inform", "der", "-in", "ca-cert.pem", + "-outform", "der", "-out", "out.txt") + self._clean("out.txt") + self.assertNotEqual(r.returncode, 0) + + def test_4f_nonexistent_file_pem(self): + r = run_wolfssl("x509", "-inform", "pem", "-in", "ca-cert.pem", + "-outform", "pem", "-out", "out.txt") + self._clean("out.txt") + self.assertNotEqual(r.returncode, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/x509/x509-process-test.sh b/tests/x509/x509-process-test.sh deleted file mode 100755 index b20d3958..00000000 --- a/tests/x509/x509-process-test.sh +++ /dev/null @@ -1,367 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -test_case() { - echo "testing: ./wolfssl -x509 $1" - OUTPUT=$(./wolfssl -x509 $1) - RESULT=$? - if [ $RESULT != 0 ]; then - echo "Failed when expected to pass" - exit 99 - fi -} - -fail_case() { - echo "testing: ./wolfssl -x509 $1" - OUTPUT=$(./wolfssl -x509 $1) - RESULT=$? - if [ $RESULT == 0 ]; then - echo "Success when expected to fail" - exit 99 - fi -} - -cert_test_case() { - echo "testing: ./wolfssl -x509 $1" - OUTPUT=$(./wolfssl -x509 $1) - RESULT=$? - echo "RESULT: $RESULT" - diff $2 $3 - RESULT=$? - echo "RESULT OF DIFF: $RESULT" - [ $RESULT != 0 ] && echo "DIFF FAILED" && exit 5 - echo "" -} - -# For check_cert_signature to perform a meaningful check, it needs the public -# key used to sign the cert (i.e. the cert must be self-signed). -check_cert_signature() { - local FAILED=0 - - echo "Checking certificate $1's signature." - - # Use OpenSSL to convert to PEM to remove any leading text in the - # certificate file or to convert DER to PEM. - openssl x509 -in $1 -out cert_stripped.pem -outform PEM - - # Extract the hex of the signature from the cert. OpenSSL 3+ uses - # 'Signature Value' for the signature label string - openssl x509 -in cert_stripped.pem -text -noout \ - -certopt ca_default -certopt no_validity \ - -certopt no_serial -certopt no_subject \ - -certopt no_extensions -certopt no_signame | \ - grep -v 'Signature Algorithm' | \ - grep -v 'Signature Value' | \ - tr -d '[:space:]:' > cert_sig_hex.bin - # Convert hex string to binary file. - cat cert_sig_hex.bin | xxd -r -p > cert_sig.bin - # Write the certificate body to a binary file. - openssl asn1parse -in cert_stripped.pem -strparse 4 \ - -out cert_body.bin -noout - RESULT=$? - if [ $RESULT != 0 ]; then - echo "Failed to extract certificate body from $1." - FAILED=1 - fi - if [ $FAILED == 0 ]; then - # Extract the public key from the cert. - openssl x509 -in cert_stripped.pem -noout -pubkey > cert_pub.pem - RESULT=$? - if [ $RESULT != 0 ]; then - echo "Failed to extract public key from $1." - FAILED=1 - fi - fi - if [ $FAILED == 0 ]; then - # Verify the signature. - openssl dgst -$2 -verify cert_pub.pem \ - -signature cert_sig.bin cert_body.bin - RESULT=$? - if [ $RESULT != 0 ]; then - echo "Signature for $1 is bad." - FAILED=1 - fi - fi - - if [ $FAILED == 1 ]; then - exit 99 - fi - - rm -f cert_sig.bin - rm -f cert_sig_hex.bin - rm -f cert_body.bin - rm -f cert_pub.pem -} - -run1() { - echo "TEST 1: VALID" - echo "TEST 1.a" - test_case "-inform pem -outform pem -in certs/ca-cert.pem -out test.pem" - # Check PEM -> PEM didn't alter any data by checking the validity of the - # signature. - check_cert_signature test.pem sha256 - if [ ! -f test.pem ]; then - echo "issue creating output file" - exit 99 - fi - echo "" - - echo "TEST 1.b" - ./wolfssl x509 -in test.pem -text -noout -out test.pem - ./wolfssl x509 -in certs/ca-cert.pem -text -noout -out ca-cert.pem - diff "./ca-cert.pem" "./test.pem" &> /dev/null - if [ $? != 0 ]; then - echo "issue with in pem out pem matching" - exit 99 - fi - rm -f ca-cert.pem - rm -f test.pem - echo "" - - echo "TEST 1.c" - test_case "-inform pem -outform der -in certs/ca-cert.pem -out test.der" - # Check PEM -> DER didn't alter any data - check_cert_signature test.der sha256 - rm -f test.der - echo "" - - echo "TEST 1.d" - test_case "-inform der -outform pem -in certs/ca-cert.der" - echo "" - - echo "TEST 1.e" - test_case "-inform der -outform der -in certs/ca-cert.der -out test.der" - # Check DER -> DER didn't alter any data - check_cert_signature test.der sha256 - rm -f test.der - echo "" - - echo "TEST 1.f" - test_case "-inform der -text -noout -in certs/ca-cert.der" - echo "" - - echo "TEST 1.g" - test_case "-inform der -pubkey -noout -in certs/ca-cert.der" - echo "" - - echo "TEST 1.h" - test_case "-inform der -outform pem -in certs/ca-cert.der -out test.pem" - # Check DER -> PEM didn't alter any data - check_cert_signature test.pem sha256 - echo "" - - echo "TEST 1.i" - cat ./certs/ca-key.pem > combined.pem - cat ./certs/ca-cert.pem >> combined.pem - test_case "-in combined.pem -out process_x509.pem" - test_case "-in process_x509.pem -text" - echo -e $OUTPUT > ./process_x509.pem - - test_case "-in ./certs/ca-cert.pem -text" - echo -e $OUTPUT > ./process_ca-cert.pem - diff ./process_ca-cert.pem ./process_x509.pem - if [ $? -ne 0 ]; then - echo "Unexpected output difference" - exit 99 - fi - - MODULUS=`cat tests/x509/expect-modulus.txt` - if [ "$MODULUS" != "Modulus=C09508E15741F2716DB7D24541270165C645AEF2BC2430B895CE2F4ED6F61C88BC7C9FFBA8677FFE5C9C5175F78ACA07E7352F8FE1BD7BC02F7CAB64A817FCCA5D7BBAE021E5722E6F2E86D89573DAAC1B53B95F3FD7190D254FE16363518B0B643FAD43B8A51C5C34B3AE00A063C5F67F0B59687873A68C18A9026DAFC319012EB810E3C6CC40B469A3463369876EC4BB17A6F3E8DDAD73BC7B2F21B5FD66510CBD54B3E16D5F1CBC2373D109038914D210B964C32AD0A1964ABCE1D41A5BC7A0C0C163780F443730329680322395A177BA13D29773E25D25C96A0DC33960A4B4B069424209E9D808BC3320B35822A7AAEBC4E1E66183C5D296DFD9D04FADD7" ] - then - echo "found unexpected Modulus : $MODULUS" - exit 99 - fi - - - rm -f combined.pem - rm -f process_x509.pem - rm -f process_ca-cert.pem - echo "" -} - -run2() { - echo "TEST 2: INVALID INPUT" - echo "TEST 2.a" - fail_case "-inform pem -inform der" - echo "TEST 2.b" - fail_case "-outform pem -outform der" - echo "TEST 2.c" - fail_case "-inform -inform" - echo "TEST 2.d" - fail_case "-outform -outform" - echo "TEST 2.e" - fail_case "-inform pem -inform der -inform" - echo "TEST 2.f" - fail_case "-outform pem -outform der -outform" - echo "TEST 2.g" - fail_case "-inform pem -outform der -inform" - echo "TEST 2.h" - fail_case "-outform pem -inform der -outform" - echo "TEST 2.i" - fail_case "-inform" - echo "TEST 2.j" - fail_case "-outform" - echo "TEST 2.k" - fail_case "-outform pem -outform der -noout" - echo "TEST 2.l" - fail_case "-outform -outform -noout" - echo "TEST 2.m" - fail_case "-outform pem -outform der -outform -noout" - echo "TEST 2.n" - fail_case "-inform pem -outform der -inform -noout" - echo "TEST 2.o" - fail_case "-outform pem -inform der -outform -noout" - echo "TEST 2.p" - fail_case "-outform -noout" - -# hangs waiting on stdin input (same as openssl) -# echo "TEST 2.q" -# fail_case "-inform pem -outform pem -noout" -} - -run3() { - echo "TEST3: VALID INPUT FILES" - echo "TEST 3.a" - # convert ca-cert.der to tmp.pem and compare to ca-cert.pem for valid - # transform - ./wolfssl x509 -inform pem -in certs/ca-cert.pem -outform pem -out test.pem - cert_test_case "-inform der -in certs/ca-cert.der -outform pem -out tmp.pem" \ - test.pem tmp.pem - rm -f test.pem tmp.pem - echo "TEST 3.b" - ./wolfssl x509 -inform pem -in certs/ca-cert.pem -outform der -out x509_test.der - cert_test_case "-inform pem -outform der -in certs/ca-cert.pem -out x509_tmp.der" \ - x509_test.der x509_tmp.der - rm -f x509_test.pem x509_tmp.pem x509_test.der x509_tmp.der - echo "TEST 3.c" - test_case "-in certs/server-cert.pem -subject -noout" - EXPECTED=`cat tests/x509/expect-subject.txt` - if [ "$OUTPUT" != "$EXPECTED" ]; then - echo "found unexpected $OUTPUT" - echo "expected $EXPECTED" - exit 99 - fi - echo "TEST 3.d" - test_case "-in certs/server-cert.pem -issuer -noout" - EXPECTED=`cat tests/x509/expect-issuer.txt` - if [ "$OUTPUT" != "$EXPECTED" ]; then - echo "found unexpected $OUTPUT" - echo "expected $EXPECTED" - exit 99 - fi - echo "TEST 3.e" - test_case "-in certs/ca-cert.pem -serial -noout" - EXPECTED=`cat tests/x509/expect-ca-serial.txt` - if [ "$OUTPUT" != "$EXPECTED" ]; then - echo "found unexpected $OUTPUT" - echo "expected $EXPECTED" - exit 99 - fi - echo "TEST 3.f" - test_case "-in certs/server-cert.pem -serial -noout" - EXPECTED=`cat tests/x509/expect-server-serial.txt` - if [ "$OUTPUT" != "$EXPECTED" ]; then - echo "found unexpected $OUTPUT" - echo "expected $EXPECTED" - exit 99 - fi - echo "TEST 3.g" - test_case "-in certs/server-cert.pem -dates -noout" - EXPECTED=`cat tests/x509/expect-dates.txt` - if [ "$OUTPUT" != "$EXPECTED" ]; then - echo "found unexpected $OUTPUT" - echo "expected $EXPECTED" - exit 99 - fi - echo "TEST 3.h" - test_case "-in certs/server-cert.pem -email -noout" - EXPECTED=`cat tests/x509/expect-email.txt` - if [ "$OUTPUT" != "$EXPECTED" ]; then - echo "found unexpected $OUTPUT" - echo "expected $EXPECTED" - exit 99 - fi - echo "TEST 3.i" - test_case "-in certs/server-cert.pem -fingerprint -noout" - EXPECTED=`cat tests/x509/expect-fingerprint.txt` - OUTPUT=`echo $OUTPUT | sed 's/^SHA1 of cert. DER : //'` - if [ "$OUTPUT" != "$EXPECTED" ]; then - echo "found unexpected $OUTPUT" - echo "expected $EXPECTED" - exit 99 - fi - echo "TEST 3.j" - test_case "-in certs/server-cert.pem -purpose -noout" - EXPECTED=`cat tests/x509/expect-purpose.txt` - if [ "$OUTPUT" != "$EXPECTED" ]; then - echo "found unexpected $OUTPUT" - echo "expected $EXPECTED" - exit 99 - fi - echo "TEST 3.k" - test_case "-in certs/server-cert.pem -hash -noout" - EXPECTED=`cat tests/x509/expect-hash.txt` - OLD_EXPECTED="f6cf410e" #was fixed to match OpenSSL after release 5.1.1 - if [ "$OUTPUT" != "$EXPECTED" ] && [ "$OUTPUT" != "$OLD_EXPECTED" ]; then - echo "found unexpected $OUTPUT" - echo "expected $EXPECTED" - exit 99 - fi - echo "TEST 3.l" - ./wolfssl req -new -days 3650 -key ./certs/server-key.pem -subj /O=wolfSSL/C=US/ST=WA/L=Seattle/CN=wolfSSL/OU=org-unit -out x509-process-tmp.cert -x509 - test_case "-in x509-process-tmp.cert -email -noout" - rm -f x509-process-tmp.cert -} - -run4() { - echo "TEST4: INVALID INPUT FILES" - echo "TEST 4.a" - #convert ca-cert.der to tmp.pem and compare to ca-cert.pem for valid transform - fail_case "-inform der -in certs/ca-cert.der - -in certs/ca-cert.der -outform pem -out tmp.pem" - echo "TEST 4.b" - fail_case "-inform der -in certs/ca-cert.der - -outform pem -out tmp.pem -out tmp.pem" - - echo "TEST 4.c" - fail_case "-inform pem -outform der -in certs/ca-cert.pem - -out tmp.der -out tmp.der -in certs/ca-cert.pem" - echo "TEST 4.d" - rm -f test.der - fail_case "-inform pem -in certs/ca-cert.der -outform der -out test.der" - if [ -f test.der ]; then - echo "./wolfssl x509 -inform pem -in certs/ca-cert.der -outform der -out test.der" - echo "Should not have created output file in error case!" - rm -f test.der - exit 99 - fi - rm -f test.der - echo "TEST 4.e" - fail_case "-inform der -in ca-cert.pem -outform der -out out.txt" - echo "TEST 4.f" - fail_case "-inform pem -in ca-cert.pem -outform pem -out out.txt" -} - -run1 -run2 -run3 -run4 - -rm -f out.txt -rm -f tmp.pem -rm -f tmp.der -rm -f cert_stripped.pem - -echo "Done" -exit 0 diff --git a/tests/x509/x509-req-test.py b/tests/x509/x509-req-test.py new file mode 100644 index 00000000..838df890 --- /dev/null +++ b/tests/x509/x509-req-test.py @@ -0,0 +1,729 @@ +#!/usr/bin/env python3 +"""Tests for wolfssl req and x509 -req (converted from x509-req-test.sh).""" + +import os +import shutil +import subprocess +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl + +HAS_OPENSSL = shutil.which("openssl") is not None +_SKIP_WIN = sys.platform == "win32" +_WIN_REASON = "config file paths not supported on Windows UNC shares" + +TEST_CONF = """\ +[ req ] +distinguished_name =req_distinguished_name +attributes =req_attributes +prompt =no +x509_extensions = v3_req +req_extensions = v3_req +[ req_distinguished_name ] +countryName =US +stateOrProvinceName =Montana +localityName =Bozeman +organizationName =wolfSSL +commonName = testing +[ req_attributes ] +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names +[ v3_alt_ca ] +basicConstraints = CA:TRUE +keyUsage = digitalSignature +subjectAltName = @alt_names +[ v3_alt_req_full ] +basicConstraints = CA:TRUE +keyUsage = digitalSignature +subjectAltName = @alt_names_full_skip +[alt_names] +DNS.1 = extraName +DNS.2 = alt-name +DNS.3 = thirdName +IP.1 = 2607:f8b0:400a:80b::2004 +DNS.4 = 2607:f8b0:400a:80b::2004 (google.com) +IP.2 = 127.0.0.1 +[alt_names_full_skip] +DNS.1 = extraName +DNS.2 = alt-name +DNS.4 = thirdName +IP.1 = 2607:f8b0:400a:80b::2004 +DNS.5 = 2607:f8b0:400a:80b::2004 (google.com) +IP.2 = 127.0.0.1 +DNS.6 = thirdName +DNS.7 = thirdName +DNS.8 = thirdName +DNS.9 = thirdName +DNS.10 = tenthName +""" + +TEST_PROMPT_CONF = """\ +[ req ] +distinguished_name =req_distinguished_name +attributes =req_attributes +x509_extensions = v3_req +req_extensions = v3_req +[ req_distinguished_name ] +countryName = 2 Letter Country Name +countryName_default = US +countryName_max = 2 +countryName_min = 2 +[ req_attributes ] +[ v3_req ] +basicConstraints = critical,CA:true +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names +[alt_names] +RID.1 = 1.1.1.1 +RID.2 = surname +email.1 = facts@wolfssl.com +URI.1 = facts@wolfssl.com +""" + + +def _is_fips(): + r = run_wolfssl("-v") + return "FIPS" in (r.stdout + r.stderr) + + +def _cleanup(*files): + for f in files: + if os.path.exists(f): + os.remove(f) + + +class TestReqNew(unittest.TestCase): + """Test req -new with various options.""" + + @classmethod + def setUpClass(cls): + cls.conf_file = "test_req.conf" + cls.prompt_conf_file = "test_req_prompt.conf" + with open(cls.conf_file, "w") as f: + f.write(TEST_CONF) + with open(cls.prompt_conf_file, "w") as f: + f.write(TEST_PROMPT_CONF) + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf_file, cls.prompt_conf_file) + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_req_new_with_subj(self): + """req -new -subj creates cert with correct subject.""" + tmp = "test_req_subj.cert" + self._clean(tmp) + r = run_wolfssl("req", "-new", "-days", "3650", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "O=wolfSSL/C=US/ST=WA/L=Seattle/CN=wolfSSL/OU=org-unit", + "-out", tmp, "-x509") + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("x509", "-in", tmp, "-text") + self.assertEqual(r2.returncode, 0, r2.stderr) + # Find the Subject line + subject_line = "" + for line in r2.stdout.splitlines(): + if "Subject:" in line: + subject_line = line + break + expected = " Subject: O=wolfSSL, C=US, ST=WA, L=Seattle, CN=wolfSSL, OU=org-unit" + self.assertEqual(subject_line, expected, + "Got: {!r}".format(subject_line)) + + @unittest.skipIf(_SKIP_WIN, _WIN_REASON) + def test_req_with_prompt_config(self): + """req with prompt config file creates CSR with SAN.""" + tmp_csr = "test_req_prompt.csr" + self._clean(tmp_csr) + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", self.prompt_conf_file, + "-out", tmp_csr, + stdin_data="US\n") + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("req", "-text", "-in", tmp_csr) + self.assertEqual(r2.returncode, 0, r2.stderr) + + # Check for SAN content + r3 = run_wolfssl("req", "-in", tmp_csr, "-text") + found_san = False + lines = r3.stdout.splitlines() + for i, line in enumerate(lines): + if "X509v3 Subject Alternative Name" in line: + found_san = True + # Next line should have the SAN details + if i + 1 < len(lines): + san_line = lines[i + 1] + self.assertIn("facts@wolfssl.com", san_line) + break + self.assertTrue(found_san, "SAN not found in CSR output") + + @unittest.skipIf(_SKIP_WIN, _WIN_REASON) + def test_req_with_config(self): + """req with config file succeeds.""" + tmp_csr = "test_req_conf.csr" + self._clean(tmp_csr) + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", self.conf_file, + "-out", tmp_csr, + stdin_data="US\n") + self.assertEqual(r.returncode, 0, r.stderr) + + @unittest.skipIf(_SKIP_WIN, _WIN_REASON) + def test_req_extensions_not_found_fails(self): + """req with nonexistent extensions section should fail.""" + r = run_wolfssl("req", "-new", "-extensions", "v3_alt_ca_not_found", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", self.conf_file, + "-x509", "-out", "alt_nf.crt") + self._clean("alt_nf.crt") + self.assertNotEqual(r.returncode, 0) + + @unittest.skipIf(_SKIP_WIN, _WIN_REASON) + def test_req_extensions_v3_alt_ca(self): + """req with v3_alt_ca extensions sets CA:TRUE.""" + alt_crt = "test_req_alt.crt" + self._clean(alt_crt) + r = run_wolfssl("req", "-new", "-extensions", "v3_alt_ca", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", self.conf_file, + "-x509", "-out", alt_crt) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("x509", "-in", alt_crt, "-text", "-noout") + self.assertEqual(r2.returncode, 0, r2.stderr) + self.assertIn("CA:TRUE", r2.stdout) + + +class TestReqPemDerRoundTrip(unittest.TestCase): + """Test PEM <-> DER round-trip for CSR.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + @classmethod + def setUpClass(cls): + cls.conf_file = "test_req_rt.conf" + with open(cls.conf_file, "w") as f: + f.write(TEST_CONF) + cls.csr = "test_req_rt.csr" + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", cls.conf_file, + "-out", cls.csr, + stdin_data="US\n") + assert r.returncode == 0, "setup CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf_file, cls.csr) + + def test_pem_to_der_to_pem(self): + """CSR PEM -> DER -> PEM round-trip produces identical output.""" + der_file = "test_req_rt.csr.der" + pem_file = "test_req_rt.csr.pem" + self._clean(der_file, pem_file) + + r = run_wolfssl("req", "-inform", "pem", "-outform", "der", + "-in", self.csr, "-out", der_file) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("req", "-inform", "der", "-outform", "pem", + "-in", der_file, "-out", pem_file) + self.assertEqual(r.returncode, 0, r.stderr) + + with open(pem_file) as f1, open(self.csr) as f2: + self.assertEqual(f1.read(), f2.read(), + "PEM -> DER -> PEM round-trip mismatch") + + +class TestX509ReqSign(unittest.TestCase): + """Test x509 -req -signkey signing.""" + + @classmethod + def setUpClass(cls): + cls.conf_file = "test_x509req_sign.conf" + with open(cls.conf_file, "w") as f: + f.write(TEST_CONF) + cls.csr = "test_x509req_sign.csr" + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", cls.conf_file, + "-out", cls.csr, + stdin_data="US\n") + assert r.returncode == 0, "setup CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf_file, cls.csr) + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_x509_in_csr_no_req_flag_fails(self): + """x509 -in csr without -req should fail.""" + r = run_wolfssl("x509", "-in", self.csr, "-days", "3650", + "-out", "tmp_sign.cert") + self._clean("tmp_sign.cert") + self.assertNotEqual(r.returncode, 0) + + def test_x509_req_without_signkey_fails(self): + """x509 -req without -signkey should fail.""" + r = run_wolfssl("x509", "-req", "-in", self.csr, "-days", "3650", + "-out", "tmp_sign.cert") + self._clean("tmp_sign.cert") + self.assertNotEqual(r.returncode, 0) + + def test_x509_in_csr_signkey_no_req_fails(self): + """x509 -in csr -signkey without -req should fail.""" + r = run_wolfssl("x509", "-in", self.csr, "-days", "3650", + "-signkey", os.path.join(CERTS_DIR, "server-key.pem"), + "-out", "tmp_sign.cert") + self._clean("tmp_sign.cert") + self.assertNotEqual(r.returncode, 0) + + def test_x509_req_signkey_succeeds(self): + """x509 -req -signkey succeeds.""" + out = "tmp_x509req_sign.cert" + self._clean(out) + r = run_wolfssl("x509", "-req", "-in", self.csr, "-days", "3650", + "-signkey", + os.path.join(CERTS_DIR, "server-key.pem"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + + +class TestX509ReqHashAlgorithms(unittest.TestCase): + """Test hash algorithm options for x509 -req.""" + + @classmethod + def setUpClass(cls): + cls.conf_file = "test_x509req_hash.conf" + with open(cls.conf_file, "w") as f: + f.write(TEST_CONF) + cls.csr = "test_x509req_hash.csr" + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", cls.conf_file, + "-out", cls.csr, + stdin_data="US\n") + assert r.returncode == 0, "setup CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf_file, cls.csr) + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def _test_hash(self, algo): + out = "tmp_hash_{}.cert".format(algo) + self._clean(out) + r = run_wolfssl("x509", "-req", "-in", self.csr, "-days", "3650", + "-{}".format(algo), + "-signkey", + os.path.join(CERTS_DIR, "server-key.pem"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_sha1(self): + self._test_hash("sha1") + + def test_sha224(self): + self._test_hash("sha224") + + def test_sha256(self): + self._test_hash("sha256") + + def test_sha384(self): + self._test_hash("sha384") + + def test_sha512(self): + self._test_hash("sha512") + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestX509ReqExtensions(unittest.TestCase): + """Test extensions from config file for x509 -req.""" + + @classmethod + def setUpClass(cls): + cls.conf_file = "test_x509req_ext.conf" + with open(cls.conf_file, "w") as f: + f.write(TEST_CONF) + cls.csr = "test_x509req_ext.csr" + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", cls.conf_file, + "-out", cls.csr, + stdin_data="US\n") + assert r.returncode == 0, "setup CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf_file, cls.csr) + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_extfile_v3_alt_ca(self): + """x509 -req with -extfile and -extensions v3_alt_ca sets CA:TRUE.""" + out = "tmp_ext.cert" + self._clean(out) + r = run_wolfssl("x509", "-req", "-in", self.csr, "-days", "3650", + "-extfile", self.conf_file, + "-extensions", "v3_alt_ca", + "-signkey", + os.path.join(CERTS_DIR, "server-key.pem"), + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("x509", "-in", out, "-text", "-noout") + self.assertEqual(r2.returncode, 0, r2.stderr) + self.assertIn("CA:TRUE", r2.stdout) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestReqConfigSubject(unittest.TestCase): + """Test subject from config file.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_subject_from_config(self): + """req with config file produces correct subject.""" + conf = "test_req_cfg_subj.conf" + tmp = "test_req_cfg_subj.cert" + self._clean(conf, tmp) + with open(conf, "w") as f: + f.write(TEST_CONF) + + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", conf, "-x509", "-out", tmp) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("x509", "-in", tmp, "-text") + self.assertEqual(r2.returncode, 0, r2.stderr) + subject_line = "" + for line in r2.stdout.splitlines(): + if "Subject:" in line: + subject_line = line + break + expected = " Subject: C=US, ST=Montana, L=Bozeman, O=wolfSSL, CN=testing" + self.assertEqual(subject_line, expected, + "Got: {!r}".format(subject_line)) + + +class TestReqDefaultBasicConstraints(unittest.TestCase): + """Test default basic constraints extension.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_default_ca_true(self): + """req -new -x509 sets CA:TRUE by default.""" + tmp = "test_req_bc.cert" + self._clean(tmp) + r = run_wolfssl("req", "-new", "-x509", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "O=wolfSSL/C=US/ST=WA/L=Seattle/CN=wolfSSL/OU=org-unit", + "-out", tmp) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("x509", "-in", tmp, "-text", "-noout") + self.assertEqual(r2.returncode, 0, r2.stderr) + self.assertIn("CA:TRUE", r2.stdout) + + +class TestReqFIPS(unittest.TestCase): + """FIPS-conditional tests.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + @classmethod + def setUpClass(cls): + cls.conf_file = "test_req_fips.conf" + with open(cls.conf_file, "w") as f: + f.write(TEST_CONF) + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf_file) + + def test_newkey_with_passout_stdin(self): + """req -newkey rsa:2048 with -passout stdin produces ENCRYPTED key.""" + if _is_fips(): + self.skipTest("FIPS build") + tmp = "test_req_fips_passout.cert" + self._clean(tmp) + r = run_wolfssl("req", "-new", "-newkey", "rsa:2048", + "-config", self.conf_file, "-x509", + "-out", tmp, "-passout", "stdin", + stdin_data="long test password\n") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("ENCRYPTED", r.stdout + r.stderr) + + def test_newkey_keyout_with_passout(self): + """req -newkey -keyout with -passout produces encrypted key.""" + if _is_fips(): + self.skipTest("FIPS build") + tmp = "test_req_fips_keyout.cert" + key = "test_req_fips_newkey.pem" + self._clean(tmp, key) + r = run_wolfssl("req", "-newkey", "rsa:2048", "-keyout", key, + "-config", self.conf_file, "-out", tmp, + "-passout", "pass:123456789wolfssl", + "-outform", "pem", "-sha256") + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("rsa", "-in", key, + "-passin", "pass:123456789wolfssl") + self.assertEqual(r2.returncode, 0, r2.stderr) + + def test_newkey_with_passout_keyout(self): + """req -newkey rsa:2048 -keyout with -passout stdin.""" + if _is_fips(): + self.skipTest("FIPS build") + tmp = "test_req_fips_ko2.cert" + key = "test_req_fips_ko2.pem" + self._clean(tmp, key) + r = run_wolfssl("req", "-new", "-newkey", "rsa:2048", + "-keyout", key, "-config", self.conf_file, + "-x509", "-out", tmp, "-passout", "stdin", + stdin_data="long test password\n") + self.assertEqual(r.returncode, 0, r.stderr) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestReqHashAndKeyAlgos(unittest.TestCase): + """Test hash and key algorithm options for req.""" + + @classmethod + def setUpClass(cls): + cls.conf_file = "test_req_algo.conf" + with open(cls.conf_file, "w") as f: + f.write(TEST_CONF) + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf_file) + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def _test_algo(self, algo_flag): + tmp = "test_req_algo_{}.cert".format(algo_flag) + self._clean(tmp) + r = run_wolfssl("req", "-new", "-days", "3650", + "-{}".format(algo_flag), + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", self.conf_file, "-out", tmp, "-x509") + self.assertEqual(r.returncode, 0, r.stderr) + + def test_rsa(self): + self._test_algo("rsa") + + def test_ed25519(self): + self._test_algo("ed25519") + + def test_sha(self): + self._test_algo("sha") + + def test_sha224(self): + self._test_algo("sha224") + + def test_sha256(self): + self._test_algo("sha256") + + def test_sha384(self): + self._test_algo("sha384") + + def test_sha512(self): + self._test_algo("sha512") + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestReqAltNamesFullSkip(unittest.TestCase): + """Test full alt_names extension with skipped indices.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + @classmethod + def setUpClass(cls): + cls.conf_file = "test_req_altfull.conf" + with open(cls.conf_file, "w") as f: + f.write(TEST_CONF) + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf_file) + + def test_v3_alt_req_full_tenthname(self): + """req with v3_alt_req_full includes tenthName.""" + tmp = "test_req_altfull.cert" + self._clean(tmp) + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "ca-key.pem"), + "-config", self.conf_file, + "-extensions", "v3_alt_req_full", + "-out", tmp) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("req", "-in", tmp, "-noout", "-text") + self.assertEqual(r2.returncode, 0, r2.stderr) + self.assertIn("tenthName", r2.stdout) + + +@unittest.skipIf(_SKIP_WIN, _WIN_REASON) +class TestReqPromptValidation(unittest.TestCase): + """Test prompt-based config validation.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + @classmethod + def setUpClass(cls): + cls.prompt_conf = "test_req_pv.conf" + with open(cls.prompt_conf, "w") as f: + f.write(TEST_PROMPT_CONF) + + @classmethod + def tearDownClass(cls): + _cleanup(cls.prompt_conf) + + def test_valid_country_code(self): + """req with valid 2-letter country code succeeds.""" + tmp = "test_req_pv_ok.cert" + self._clean(tmp) + r = run_wolfssl("req", "-new", "-x509", + "-key", os.path.join(CERTS_DIR, "ca-key.pem"), + "-config", self.prompt_conf, + "-out", tmp, + stdin_data="AA\n") + self.assertEqual(r.returncode, 0, r.stderr) + + def test_long_country_code_fails(self): + """req with too-long country code should fail.""" + tmp = "test_req_pv_fail.cert" + self._clean(tmp) + r = run_wolfssl("req", "-new", "-x509", + "-key", os.path.join(CERTS_DIR, "ca-key.pem"), + "-config", self.prompt_conf, + "-out", tmp, + stdin_data="LONG\n") + self.assertNotEqual(r.returncode, 0) + + +class TestReqCSRAttributes(unittest.TestCase): + """Test CSR attribute printing.""" + + def test_attributes_csr(self): + """req -text on attributes-csr.pem shows expected attributes.""" + csr_path = os.path.join(CERTS_DIR, "attributes-csr.pem") + if not os.path.isfile(csr_path): + self.skipTest("attributes-csr.pem not available") + + r = run_wolfssl("req", "-text", "-noout", "-in", csr_path) + if r.returncode != 0: + self.skipTest("wolfSSL version does not support CSR attributes") + + output = r.stdout + self.assertIn("initials", output) + self.assertIn("abc", output) + self.assertIn("dnQualifier", output) + self.assertIn("dn", output) + self.assertIn("challengePassword", output) + self.assertIn("test", output) + self.assertIn("givenName", output) + self.assertIn("Given Name", output) + self.assertIn("surname", output) + + +class TestReqCSRVersion(unittest.TestCase): + """Test CSR version number.""" + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def test_csr_version(self): + """CSR version should be 1 (0x0).""" + conf = "test_req_ver.conf" + csr = "test_req_ver.csr" + self._clean(conf, csr) + with open(conf, "w") as f: + f.write(TEST_CONF) + + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", conf, "-out", csr) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("req", "-text", "-noout", "-in", csr) + if r2.returncode != 0: + self.skipTest("req -text not supported") + # Check version + found_version = False + for line in r2.stdout.splitlines(): + if "Version" in line and "1" in line and "0x0" in line: + found_version = True + break + self.assertTrue(found_version, + "Version 1 (0x0) not found in: {}".format(r2.stdout)) + + @unittest.skipUnless(HAS_OPENSSL, "openssl not available") + def test_csr_version_openssl_interop(self): + """OpenSSL should also see version 1 (0x0) in our CSR.""" + conf = "test_req_ver_ossl.conf" + csr = "test_req_ver_ossl.csr" + self._clean(conf, csr) + with open(conf, "w") as f: + f.write(TEST_CONF) + + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", conf, "-out", csr) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = subprocess.run( + ["openssl", "req", "-text", "-noout", "-in", csr], + capture_output=True, text=True, timeout=60) + if r2.returncode != 0: + self.skipTest("openssl req -text failed") + found_version = False + for line in r2.stdout.splitlines(): + if "Version" in line and "1" in line and "0x0" in line: + found_version = True + break + self.assertTrue(found_version, + "Version not found in openssl output: {}".format( + r2.stdout)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/x509/x509-req-test.sh b/tests/x509/x509-req-test.sh deleted file mode 100755 index d0d8742f..00000000 --- a/tests/x509/x509-req-test.sh +++ /dev/null @@ -1,338 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -# Is this a FIPS build? -IS_FIPS=0 -if ./wolfssl -v 2>&1 | grep -q FIPS; then - IS_FIPS=1 -fi - -run_success() { - if [ -z "$2" ]; then - RESULT=`./wolfssl $1` - else - RESULT=`echo "$2" | ./wolfssl $1` - fi - if [ $? != 0 ]; then - echo "Fail on ./wolfssl $1" - exit 99 - fi -} - -run_fail() { - if [ -z "$2" ]; then - RESULT=`./wolfssl $1` - else - RESULT=`echo "$2" | ./wolfssl $1` - fi - if [ $? == 0 ]; then - echo "Fail on ./wolfssl $1" - exit 99 - fi -} - - -cat << EOF > test.conf -[ req ] -distinguished_name =req_distinguished_name -attributes =req_attributes -prompt =no -x509_extensions = v3_req -req_extensions = v3_req -[ req_distinguished_name ] -countryName =US -stateOrProvinceName =Montana -localityName =Bozeman -organizationName =wolfSSL -commonName = testing -[ req_attributes ] -[ v3_req ] -basicConstraints = CA:FALSE -keyUsage = nonRepudiation, digitalSignature, keyEncipherment -subjectAltName = @alt_names -[ v3_alt_ca ] -basicConstraints = CA:TRUE -keyUsage = digitalSignature -subjectAltName = @alt_names -[ v3_alt_req_full ] -basicConstraints = CA:TRUE -keyUsage = digitalSignature -subjectAltName = @alt_names_full_skip -[alt_names] -DNS.1 = extraName -DNS.2 = alt-name -DNS.3 = thirdName -IP.1 = 2607:f8b0:400a:80b::2004 -DNS.4 = 2607:f8b0:400a:80b::2004 (google.com) -IP.2 = 127.0.0.1 -[alt_names_full_skip] -DNS.1 = extraName -DNS.2 = alt-name -DNS.4 = thirdName -IP.1 = 2607:f8b0:400a:80b::2004 -DNS.5 = 2607:f8b0:400a:80b::2004 (google.com) -IP.2 = 127.0.0.1 -DNS.6 = thirdName -DNS.7 = thirdName -DNS.8 = thirdName -DNS.9 = thirdName -DNS.10 = tenthName -EOF - -cat << EOF > test-prompt.conf -[ req ] -distinguished_name =req_distinguished_name -attributes =req_attributes -x509_extensions = v3_req -req_extensions = v3_req -[ req_distinguished_name ] -countryName = 2 Letter Country Name -countryName_default = US -countryName_max = 2 -countryName_min = 2 -[ req_attributes ] -[ v3_req ] -basicConstraints = critical,CA:true -keyUsage = nonRepudiation, digitalSignature, keyEncipherment -subjectAltName = @alt_names -[alt_names] -RID.1 = 1.1.1.1 -RID.2 = surname -email.1 = facts@wolfssl.com -URI.1 = facts@wolfssl.com - -EOF - - -run_success "req -new -days 3650 -key ./certs/server-key.pem -subj O=wolfSSL/C=US/ST=WA/L=Seattle/CN=wolfSSL/OU=org-unit -out tmp.cert -x509" - -SUBJECT=`./wolfssl x509 -in tmp.cert -text | grep Subject:` -EXPECTED=" Subject: O=wolfSSL, C=US, ST=WA, L=Seattle, CN=wolfSSL, OU=org-unit" -if [ "$SUBJECT" != "$EXPECTED" ] -then - echo "found unexpected result" - echo "Got : $SUBJECT" - echo "Expected : $EXPECTED" - exit 99 -fi -rm -f tmp.cert - -# no parameter -conf -#run_fail "req -new -key ./certs/server-key.pem -conf ./test.conf -out tmp.csr" - -run_success "req -new -key ./certs/server-key.pem -config ./test-prompt.conf -out tmp.csr" "US" -run_success "req -text -in tmp.csr" -SUBJECT=`./wolfssl req -in tmp.csr -text | grep -A1 "X509v3 Subject Alternative Name"` -EXPECTED=" X509v3 Subject Alternative Name: - email:facts@wolfssl.com, URI:facts@wolfssl.com, Registered ID:surname, Registered ID:1.1.1.1" -if [ "$SUBJECT" != "$EXPECTED" ] -then - echo "found unexpected result" - echo "Got : $SUBJECT" - echo "Expected : $EXPECTED" - exit 99 -fi - -run_success "req -new -key ./certs/server-key.pem -config ./test.conf -out tmp.csr" "US" - - -# fail when extensions can not be found -run_fail "req -new -extensions v3_alt_ca_not_found -key ./certs/server-key.pem -config ./test.conf -x509 -out alt.crt" -run_success "req -new -extensions v3_alt_ca -key ./certs/server-key.pem -config ./test.conf -x509 -out alt.crt" -run_success "x509 -in alt.crt -text -noout" -echo "$RESULT" | grep "CA:TRUE" -if [ $? != 0 ]; then - echo "was expecting alt extensions to have CA set" - exit 99 -fi - -# test pem to der and back again -run_success "req -inform pem -outform der -in tmp.csr -out tmp.csr.der" -run_success "req -inform der -outform pem -in tmp.csr.der -out tmp.csr.pem" -diff tmp.csr.pem tmp.csr -if [ $? != 0 ]; then - echo "transforming from der and back to pem mismatch" - echo "tmp.csr != tmp.csr.pem" - exit 99 -fi -rm -f tmp.csr.pem -rm -f tmp.csr.der - -# test passing csr file for x509 -run_fail "x509 -in tmp.csr -days 3650 -out tmp.cert" -run_fail "x509 -in tmp.csr -days 3650 -signkey ./certs/server-key.pem -out tmp.cert" -run_fail "x509 -req -in tmp.csr -days 3650 -out tmp.cert" -run_success "x509 -req -in tmp.csr -days 3650 -signkey ./certs/server-key.pem -out tmp.cert" -rm -f tmp.cert - - -#testing hash for x509 -run_success "x509 -req -in tmp.csr -days 3650 -sha1 -signkey ./certs/server-key.pem -out tmp.cert" -rm -f tmp.cert -run_success "x509 -req -in tmp.csr -days 3650 -sha224 -signkey ./certs/server-key.pem -out tmp.cert" -rm -f tmp.cert -run_success "x509 -req -in tmp.csr -days 3650 -sha256 -signkey ./certs/server-key.pem -out tmp.cert" -rm -f tmp.cert -run_success "x509 -req -in tmp.csr -days 3650 -sha384 -signkey ./certs/server-key.pem -out tmp.cert" -rm -f tmp.cert -run_success "x509 -req -in tmp.csr -days 3650 -sha512 -signkey ./certs/server-key.pem -out tmp.cert" -rm -f tmp.cert - -#testing extensions for x509 -run_success "x509 -req -in tmp.csr -days 3650 -extfile ./test.conf -extensions v3_alt_ca -signkey ./certs/server-key.pem -out tmp.cert" -run_success "x509 -in tmp.cert -text -noout" -echo "$RESULT" | grep "CA:TRUE" -if [ $? != 0 ]; then - echo "was expecting alt extensions to have CA set" - exit 99 -fi - -rm -f tmp.cert -rm -f tmp.csr -rm -f alt.crt - -run_success "req -new -key ./certs/server-key.pem -config ./test.conf -x509 -out tmp.cert" -SUBJECT=`./wolfssl x509 -in tmp.cert -text | grep Subject:` -EXPECTED=" Subject: C=US, ST=Montana, L=Bozeman, O=wolfSSL, CN=testing" -if [ "$SUBJECT" != "$EXPECTED" ] -then - echo "found unexpected result" - echo "Got : $SUBJECT" - echo "Expected : $EXPECTED" - exit 99 -fi -rm -f tmp.cert - -# test default basic constraints extenstion -run_success "req -new -x509 -key certs/server-key.pem -subj O=wolfSSL/C=US/ST=WA/L=Seattle/CN=wolfSSL/OU=org-unit -out tmp.cert" -run_success "x509 -in tmp.cert -text -noout" -echo "$RESULT" | grep "CA:TRUE" -if [ $? != 0 ]; then - echo "was expecting cert extensions to have CA set to TRUE" - exit 99 -fi -rm -f tmp.cert - - -if [ ${IS_FIPS} != "1" ]; then - run_success "req -new -newkey rsa:2048 -config ./test.conf -x509 -out tmp.cert -passout stdin" "long test password" - echo $RESULT | grep "ENCRYPTED" - if [ $? -ne 0 ]; then - echo "no encrypted key found in result" - exit 99 - fi - rm -f tmp.cert -fi - -#testing hash and key algos -run_success "req -new -days 3650 -rsa -key ./certs/server-key.pem -config ./test.conf -out tmp.cert -x509" -rm -f tmp.cert -run_success "req -new -days 3650 -ed25519 -key ./certs/server-key.pem -config ./test.conf -out tmp.cert -x509" -rm -f tmp.cert -run_success "req -new -days 3650 -sha -key ./certs/server-key.pem -config ./test.conf -out tmp.cert -x509" -rm -f tmp.cert -run_success "req -new -days 3650 -sha224 -key ./certs/server-key.pem -config ./test.conf -out tmp.cert -x509" -rm -f tmp.cert -run_success "req -new -days 3650 -sha256 -key ./certs/server-key.pem -config ./test.conf -out tmp.cert -x509" -rm -f tmp.cert -run_success "req -new -days 3650 -sha384 -key ./certs/server-key.pem -config ./test.conf -out tmp.cert -x509" -rm -f tmp.cert -run_success "req -new -days 3650 -sha512 -key ./certs/server-key.pem -config ./test.conf -out tmp.cert -x509" -rm -f tmp.cert - -if [ ${IS_FIPS} != "1" ]; then - run_success "req -new -newkey rsa:2048 -keyout new-key.pem -config ./test.conf -x509 -out tmp.cert -passout stdin" "long test password" -fi - -run_success "req -new -key ./certs/ca-key.pem -config ./test.conf -extensions v3_alt_req_full -out tmp.cert" -run_success "req -in ./tmp.cert -noout -text" -echo $RESULT | grep tenthName -if [ $? -ne 0 ]; then - echo Failed to find tenthName in alt names - exit 99 -fi - -if [ ${IS_FIPS} != "1" ]; then -#test passout - run_success "req -newkey rsa:2048 -keyout new-key.pem -config ./test.conf -out tmp.cert -passout pass:123456789wolfssl -outform pem -sha256" - run_success "rsa -in new-key.pem -passin pass:123456789wolfssl" -fi - -run_success "req -new -x509 -key ./certs/ca-key.pem -config ./test-prompt.conf -out tmp.cert" "AA" -run_fail "req -new -x509 -key ./certs/ca-key.pem -config ./test-prompt.conf -out tmp.cert" "LONG" - -rm -f tmp.cert -rm -f new-key.pem -rm -f test.conf -rm -f test-prompt.conf - -# test printing out CSR attributes, older versions of wolfSSL will fail this -RESULT=`./wolfssl req -text -noout -in ./certs/attributes-csr.pem` -if [ $? -eq 0 ]; then - echo $RESULT | grep "initials" | grep "abc" - if [ $? -ne 0 ]; then - echo "no initials attribute found" - exit 99 - fi - echo $RESULT | grep "dnQualifier" | grep "dn" - if [ $? -ne 0 ]; then - echo "no dnQualifier attribute found" - exit 99 - fi - echo $RESULT | grep "challengePassword" | grep "test" - if [ $? -ne 0 ]; then - echo "no challengePassword attribute found" - exit 99 - fi - echo $RESULT | grep "givenName" | grep "Given Name" - if [ $? -ne 0 ]; then - echo "no givenName attribute found" - exit 99 - fi - echo $RESULT | grep "surname" - if [ $? -ne 0 ]; then - echo "no surname attribute found" - exit 99 - fi - -fi - -# test csr version -run_success "req -new -key ./certs/server-key.pem -config ./test.conf -out tmp.csr" -RESULT=`./wolfssl req -text -noout -in tmp.csr` -if [ $? -eq 0 ]; then - # also check that the version is fine. - echo $RESULT | grep "Version" | grep "1" | grep "0x0" - if [ $? -ne 0 ]; then - echo "Printing wrong version number" - exit 99 - fi -fi - -# now make sure that openssl also sees what we see. -RESULT=`openssl req -text -noout -in tmp.csr` -if [ $? -eq 0 ]; then - echo $RESULT | grep "Version" | grep "1" | grep "0x0" - if [ $? -ne 0 ]; then - echo "Printing wrong version number" - exit 99 - fi -fi -rm -f tmp.cert -rm -f tmp.csr - -echo "Done" -exit 0 - - diff --git a/tests/x509/x509-verify-test.py b/tests/x509/x509-verify-test.py new file mode 100644 index 00000000..1a40a97a --- /dev/null +++ b/tests/x509/x509-verify-test.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Tests for wolfssl verify (converted from x509-verify-test.sh).""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from wolfclu_test import CERTS_DIR, run_wolfssl + + +def _has_crl(): + """Check whether CRL support is compiled in.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + "-crl_check", + os.path.join(CERTS_DIR, "server-cert.pem")) + combined = r.stdout + r.stderr + return "recompile wolfSSL with CRL" not in combined + + +class TestX509Verify(unittest.TestCase): + """Certificate verification tests.""" + + def test_verify_without_ca_fails(self): + """verify server-cert.pem without CA should fail with issuer error.""" + r = run_wolfssl("verify", + os.path.join(CERTS_DIR, "server-cert.pem")) + self.assertNotEqual(r.returncode, 0) + combined = r.stdout + r.stderr + self.assertIn("unable to get local issuer certificate", combined) + + def test_verify_ca_cert_self_signed_error(self): + """verify ca-cert.pem alone should fail with self-signed error.""" + r = run_wolfssl("verify", + os.path.join(CERTS_DIR, "ca-cert.pem")) + self.assertNotEqual(r.returncode, 0) + combined = r.stdout + r.stderr + self.assertIn("self-signed certificate in certificate chain", combined) + + def test_verify_with_correct_cafile(self): + """verify with correct CAfile succeeds.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + os.path.join(CERTS_DIR, "server-cert.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_verify_with_wrong_cafile_ecc(self): + """verify ECC cert with RSA CA should fail.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + os.path.join(CERTS_DIR, "server-ecc.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_verify_ecc_cert(self): + """verify ECC cert with correct ECC CA succeeds.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-ecc-cert.pem"), + os.path.join(CERTS_DIR, "server-ecc.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_verify_rsa_again(self): + """verify RSA cert with RSA CA succeeds (repeat).""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + os.path.join(CERTS_DIR, "server-cert.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_verify_self_as_ca_fails(self): + """verify server-cert.pem as its own CA should fail.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "server-cert.pem"), + os.path.join(CERTS_DIR, "server-cert.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_verify_partial_chain(self): + """verify with -partial_chain allows self as CA.""" + r = run_wolfssl("verify", "-partial_chain", "-CAfile", + os.path.join(CERTS_DIR, "server-cert.pem"), + os.path.join(CERTS_DIR, "server-cert.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + +class TestX509VerifyCRL(unittest.TestCase): + """CRL-related verification tests.""" + + @classmethod + def setUpClass(cls): + cls.have_crl = _has_crl() + + def setUp(self): + if not self.have_crl: + self.skipTest("CRL not compiled in") + + def test_crl_check_no_crl_loaded_fails(self): + """crl_check with no CRL loaded should fail.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + "-crl_check", + os.path.join(CERTS_DIR, "server-cert.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_crl_check_with_chain(self): + """crl_check with CRL chain succeeds.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "crl-chain.pem"), + "-crl_check", + os.path.join(CERTS_DIR, "server-cert.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_crl_check_revoked_fails(self): + """crl_check on revoked cert should fail.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "crl-chain.pem"), + "-crl_check", + os.path.join(CERTS_DIR, "server-revoked-cert.pem")) + self.assertNotEqual(r.returncode, 0) + + +class TestX509VerifyChain(unittest.TestCase): + """Certificate chain verification tests.""" + + def test_intermediate_without_root_fails(self): + """Verifying int2 with int1 as CA (no root) should fail.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-int-cert.pem"), + os.path.join(CERTS_DIR, "ca-int2-cert.pem")) + self.assertNotEqual(r.returncode, 0) + + def test_intermediate_partial_chain(self): + """Verifying int2 with int1 as CA and -partial_chain succeeds.""" + r = run_wolfssl("verify", "-partial_chain", "-CAfile", + os.path.join(CERTS_DIR, "ca-int-cert.pem"), + os.path.join(CERTS_DIR, "ca-int2-cert.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_client_int_partial_chain(self): + """Verifying client-int-cert with int2 as CA and -partial_chain.""" + r = run_wolfssl("verify", "-partial_chain", "-CAfile", + os.path.join(CERTS_DIR, "ca-int2-cert.pem"), + os.path.join(CERTS_DIR, "client-int-cert.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_untrusted_chain(self): + """Verifying with -untrusted intermediate succeeds.""" + r = run_wolfssl("verify", "-CAfile", + os.path.join(CERTS_DIR, "ca-cert.pem"), + "-untrusted", + os.path.join(CERTS_DIR, "ca-int-cert.pem"), + os.path.join(CERTS_DIR, "ca-int2-cert.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/x509/x509-verify-test.sh b/tests/x509/x509-verify-test.sh deleted file mode 100755 index 73e8c4e0..00000000 --- a/tests/x509/x509-verify-test.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/bash - -if [ ! -d ./certs/ ]; then - #return 77 to indicate to automake that the test was skipped - exit 77 -fi - -# Skip test if filesystem disabled -FILESYSTEM=`cat config.log | grep "disable\-filesystem"` -if [ "$FILESYSTEM" != "" ] -then - exit 77 -fi - -RESULT=`./wolfssl verify ./certs/server-cert.pem 2>&1` -if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl verify ./certs/server-cert.pem\"" - exit 99 -fi -echo "$RESULT" | grep "Err (20): unable to get local issuer certificate" -if [ $? != 0 ]; then - echo "Unexpected error result on test \"./wolfssl verify ./certs/server-cert.pem\"" - exit 99 -fi - -RESULT=`./wolfssl verify ./certs/ca-cert.pem 2>&1` -if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl verify ./certs/ca-cert.pem\"" - exit 99 -fi -echo "$RESULT" | grep "Err (18): self-signed certificate in certificate chain" -if [ $? != 0 ]; then - echo "Unexpected error result on test \"./wolfssl verify ./certs/ca-cert.pem\"" - exit 99 -fi - -RESULT=`./wolfssl verify -CAfile ./certs/ca-cert.pem ./certs/server-cert.pem` -if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl verify -CAfile ./certs/ca-cert.pem ./certs/server-cert.pem\"" - exit 99 -fi - -RESULT=`./wolfssl verify -CAfile ./certs/ca-cert.pem ./certs/server-ecc.pem` -if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl verify -CAfile ./certs/ca-cert.pem ./certs/server-ecc.pem\"" - exit 99 -fi - -RESULT=`./wolfssl verify -CAfile ./certs/ca-ecc-cert.pem ./certs/server-ecc.pem` -if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl verify -CAfile ./certs/ca-ecc-cert.pem ./certs/server-ecc.pem\"" - exit 99 -fi - -RESULT=`./wolfssl verify -CAfile ./certs/ca-cert.pem ./certs/server-cert.pem` -if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl verify -CAfile ./certs/ca-cert.pem ./certs/server-cert.pem\"" - exit 99 -fi - -RESULT=`./wolfssl verify -CAfile ./certs/server-cert.pem ./certs/server-cert.pem` -if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl verify -CAfile ./certs/server-cert.pem ./certs/server-cert.pem\"" - exit 99 -fi - -RESULT=`./wolfssl verify -partial_chain -CAfile ./certs/server-cert.pem ./certs/server-cert.pem` -if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl verify -partial_chain -CAfile ./certs/server-cert.pem ./certs/server-cert.pem\"" - exit 99 -fi - -RESULT=`./wolfssl verify -CAfile ./certs/ca-cert.pem -crl_check ./certs/server-cert.pem 2>&1 | grep "recompile wolfSSL with CRL"` -HAVE_CRL=$? - -#if the return value of the grep is success (0) then CRL not compiled in -if [ $HAVE_CRL != 0 ]; then - RESULT=`./wolfssl verify -CAfile ./certs/ca-cert.pem -crl_check ./certs/server-cert.pem` - if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl verify -CAfile ./certs/ca-cert.pem -crl_check ./certs/server-cert.pem\"" - exit 99 - fi - - RESULT=`./wolfssl verify -CAfile ./certs/crl-chain.pem -crl_check ./certs/server-cert.pem` - if [ $? != 0 ]; then - echo "Failed on test \"./wolfssl verify -CAfile ./certs/crl-chain.pem -crl_check ./certs/server-cert.pem\"" - exit 99 - fi - - RESULT=`./wolfssl verify -CAfile ./certs/crl-chain.pem -crl_check ./certs/server-revoked-cert.pem` - if [ $? == 0 ]; then - echo "Failed on test \"./wolfssl verify -CAfile ./certs/crl-chain.pem -crl_check ./certs/server-revoked-cert.pem\"" - exit 99 - fi -else - echo "Skipping CRL tests..." -fi - -# Test verifying along a chain of certificates -RESULT=`./wolfssl verify -CAfile ./certs/ca-int-cert.pem ./certs/ca-int2-cert.pem` -if [ $? == 0 ]; then - echo "Should have failed to verify ca-int2-cert.pem with ca-int-cert.pem" - exit 99 -fi -RESULT=`./wolfssl verify -partial_chain -CAfile ./certs/ca-int-cert.pem ./certs/ca-int2-cert.pem` -if [ $? != 0 ]; then - echo "Failed to verify ca-int2-cert.pem with ca-int-cert.pem" - exit 99 -fi -RESULT=`./wolfssl verify -partial_chain -CAfile ./certs/ca-int2-cert.pem ./certs/client-int-cert.pem` -if [ $? != 0 ]; then - echo "Failed to verify client-int-cert.pem with ca-int2-cert.pem" - exit 99 -fi -RESULT=`./wolfssl verify -CAfile ./certs/ca-cert.pem -untrusted ./certs/ca-int-cert.pem ./certs/ca-int2-cert.pem` -if [ $? != 0 ]; then - echo "Failed to verify ca-int2-cert.pem with ca-cert.pem and ca-int-cert.pem" - exit 99 -fi - -exit 0 From 75e45d16581c4e8bc85a9056a505a1a5c6817815 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 31 Mar 2026 14:19:29 +0200 Subject: [PATCH 04/17] Run tests in windows CI --- .github/workflows/windows-check.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/windows-check.yml b/.github/workflows/windows-check.yml index bae3bd50..d4891461 100644 --- a/.github/workflows/windows-check.yml +++ b/.github/workflows/windows-check.yml @@ -62,3 +62,7 @@ jobs: # Add additional options to the MSBuild command line here (like platform or verbosity level). # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference run: msbuild /m /p:PlatformToolset=v142 /p:Platform=${{env.BUILD_PLATFORM}} /p:WindowsTargetPlatformVersion=${{env.TARGET_PLATFORM}} /p:Configuration=${{env.WOLFCLU_BUILD_CONFIGURATION}} ${{env.SOLUTION_FILE_PATH}} + + - name: Run tests + working-directory: ${{env.GITHUB_WORKSPACE}}wolfclu + run: python tests/run_tests.py From 2b1c91cedbddfaaa6afe125394885ef9537bc411 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 31 Mar 2026 14:56:49 +0200 Subject: [PATCH 05/17] Undef CRL_REASON_* because it clashes with other definitions --- wolfclu/clu_header_main.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/wolfclu/clu_header_main.h b/wolfclu/clu_header_main.h index c3d08185..5fe14925 100644 --- a/wolfclu/clu_header_main.h +++ b/wolfclu/clu_header_main.h @@ -43,6 +43,18 @@ extern "C" { #endif #endif +#undef CRL_REASON_UNSPECIFIED +#undef CRL_REASON_KEY_COMPROMISE +#undef CRL_REASON_CA_COMPROMISE +#undef CRL_REASON_AFFILIATION_CHANGED +#undef CRL_REASON_SUPERSEDED +#undef CRL_REASON_CESSATION_OF_OPERATION +#undef CRL_REASON_CERTIFICATE_HOLD +#undef CRL_REASON_REMOVE_FROM_CRL +#undef CRL_REASON_PRIVILEGE_WITHDRAWN +#undef CRL_REASON_AA_COMPROMISE + + /* wolfssl includes */ #ifndef WOLFSSL_USER_SETTINGS #include From b28b79d67993a2e56650f1b61f40d24d5c25ee48 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 31 Mar 2026 16:24:24 +0200 Subject: [PATCH 06/17] Fix test failures --- .gitattributes | 8 ++++++++ .github/workflows/fsanitize-check.yml | 1 - src/crypto/clu_decrypt.c | 2 +- src/x509/clu_x509_sign.c | 10 ---------- tests/client/client-test.py | 18 ++++++++++++------ tests/x509/x509-process-test.py | 16 ++++++++-------- 6 files changed, 29 insertions(+), 26 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..bbd3b15f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Ensure cert/key files always use LF line endings so that hashes +# remain consistent across platforms. +*.pem eol=lf +*.cnf eol=lf +*.cfg eol=lf + +# DER files are binary and must never be transformed. +*.der binary diff --git a/.github/workflows/fsanitize-check.yml b/.github/workflows/fsanitize-check.yml index e2e93eab..68b21c5b 100644 --- a/.github/workflows/fsanitize-check.yml +++ b/.github/workflows/fsanitize-check.yml @@ -82,7 +82,6 @@ jobs: env: LD_LIBRARY_PATH: ${{ github.workspace }}/build-dir/lib with: - repository: wolfssl/wolfclu path: wolfclu configure: CC="gcc -fsanitize=address" LDFLAGS="-L${{ github.workspace }}/build-dir/lib" CPPFLAGS="-I${{ github.workspace }}/build-dir/include" check: true diff --git a/src/crypto/clu_decrypt.c b/src/crypto/clu_decrypt.c index 5a5aa0f4..997d85ed 100644 --- a/src/crypto/clu_decrypt.c +++ b/src/crypto/clu_decrypt.c @@ -156,7 +156,7 @@ int wolfCLU_decrypt(int alg, char* mode, byte* pwdKey, byte* key, int size, } else { ret = (int)XFREAD(input, 1, MAX_LEN, inFile); - if ((ret > 0 && ret != MAX_LEN) || feof(inFile)) { + if (ret > 0) { tempMax = ret; ret = 0; /* success */ } diff --git a/src/x509/clu_x509_sign.c b/src/x509/clu_x509_sign.c index 038892a8..3c6bc255 100644 --- a/src/x509/clu_x509_sign.c +++ b/src/x509/clu_x509_sign.c @@ -1274,21 +1274,11 @@ int wolfCLU_CertSign(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* x509) case WC_HASH_TYPE_BLAKE2B: case WC_HASH_TYPE_BLAKE2S: - #if LIBWOLFSSL_VERSION_HEX > 0x05001000 - #ifndef WOLFSSL_NOSHA512_224 case WC_HASH_TYPE_SHA512_224: - #endif - #ifndef WOLFSSL_NOSHA512_256 case WC_HASH_TYPE_SHA512_256: - #endif - #ifdef WOLFSSL_SHAKE128 case WC_HASH_TYPE_SHAKE128: - #endif - #ifdef WOLFSSL_SHAKE256 case WC_HASH_TYPE_SHAKE256: - #endif case WC_HASH_TYPE_SM3: - #endif default: wolfCLU_LogError("Unsupported hash type"); ret = WOLFCLU_FATAL_ERROR; diff --git a/tests/client/client-test.py b/tests/client/client-test.py index c856970a..52786115 100644 --- a/tests/client/client-test.py +++ b/tests/client/client-test.py @@ -30,12 +30,18 @@ def test_s_client_x509(self): if os.path.exists(tmp_crt) else None) # Run s_client with empty stdin so it connects then disconnects - s_client = subprocess.run( - [WOLFSSL_BIN, "s_client", "-connect", "www.google.com:443"], - input=b"\n", - capture_output=True, - timeout=30, - ) + try: + s_client = subprocess.run( + [WOLFSSL_BIN, "s_client", "-connect", "www.google.com:443"], + input=b"\n", + capture_output=True, + timeout=30, + ) + except (subprocess.TimeoutExpired, OSError): + self.skipTest("s_client connection timed out or failed") + + if s_client.returncode != 0 or not s_client.stdout: + self.skipTest("s_client could not connect (no network?)") # Pipe s_client stdout into x509 to extract the cert as PEM x509_extract = subprocess.run( diff --git a/tests/x509/x509-process-test.py b/tests/x509/x509-process-test.py index 01533f8c..092ba638 100644 --- a/tests/x509/x509-process-test.py +++ b/tests/x509/x509-process-test.py @@ -14,7 +14,7 @@ HAS_OPENSSL = shutil.which("openssl") is not None -def _check_cert_signature(cert_path, digest): +def _check_cert_signature(cert_path, digest, inform="PEM"): """Use OpenSSL to verify the signature on a self-signed certificate. Returns True on success, raises AssertionError on failure. @@ -24,10 +24,13 @@ def _check_cert_signature(cert_path, digest): raise unittest.SkipTest("openssl not available") stripped = cert_path + ".stripped.pem" + sig_bin = cert_path + ".sig.bin" + body_bin = cert_path + ".body.bin" + pub_pem = cert_path + ".pub.pem" try: subprocess.run( - ["openssl", "x509", "-in", cert_path, "-out", stripped, - "-outform", "PEM"], + ["openssl", "x509", "-inform", inform, "-in", cert_path, + "-out", stripped, "-outform", "PEM"], check=True, capture_output=True, timeout=60) # Extract signature hex @@ -48,17 +51,14 @@ def _check_cert_signature(cert_path, digest): lines.append(stripped_line) sig_hex = "".join(lines) - sig_bin = cert_path + ".sig.bin" with open(sig_bin, "wb") as f: f.write(bytes.fromhex(sig_hex)) - body_bin = cert_path + ".body.bin" subprocess.run( ["openssl", "asn1parse", "-in", stripped, "-strparse", "4", "-out", body_bin, "-noout"], check=True, capture_output=True, timeout=60) - pub_pem = cert_path + ".pub.pem" with open(pub_pem, "w") as pub_f: subprocess.run( ["openssl", "x509", "-in", stripped, "-noout", "-pubkey"], @@ -150,7 +150,7 @@ def test_1c_pem_to_der_signature(self): "-in", os.path.join(CERTS_DIR, "ca-cert.pem"), "-out", out) self.assertEqual(r.returncode, 0, r.stderr) - _check_cert_signature(out, "sha256") + _check_cert_signature(out, "sha256", inform="DER") def test_1d_der_to_pem_stdout(self): """DER -> PEM to stdout succeeds.""" @@ -175,7 +175,7 @@ def test_1e_der_to_der_signature(self): "-in", os.path.join(CERTS_DIR, "ca-cert.der"), "-out", out) self.assertEqual(r.returncode, 0, r.stderr) - _check_cert_signature(out, "sha256") + _check_cert_signature(out, "sha256", inform="DER") def test_1f_der_text_noout(self): """DER text/noout succeeds.""" From be40be021e192eea2786b85fe471b4259f66ffaf Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 1 Apr 2026 14:13:39 +0200 Subject: [PATCH 07/17] Consolidate CI workflows and update action versions Replace fsanitize-check.yml, macos-check.yml, and ubuntu-check.yml with a single ci.yml that uses a matrix of OS, config, and sanitizer dimensions. Update all checkout actions to v4 and setup-msbuild to v2. --- .github/workflows/ci.yml | 70 +++++++++++++++++++++ .github/workflows/fsanitize-check.yml | 90 --------------------------- .github/workflows/macos-check.yml | 49 --------------- .github/workflows/ubuntu-check.yml | 52 ---------------- .github/workflows/windows-check.yml | 6 +- 5 files changed, 73 insertions(+), 194 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/fsanitize-check.yml delete mode 100644 .github/workflows/macos-check.yml delete mode 100644 .github/workflows/ubuntu-check.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a127d8e7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: ['*'] + pull_request: + branches: ['*'] + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + config: + - '--enable-wolfclu' + - '--enable-wolfclu --enable-crl --enable-dsa --enable-pkcs7' + - '--enable-wolfclu --enable-smallstack' + - '--enable-wolfclu --enable-experimental --enable-dilithium' + - '--enable-wolfclu --enable-smallstack --enable-experimental --enable-dilithium' + - '--enable-all' + sanitize: ['', 'CC="cc -fsanitize=address"'] + + name: ${{ matrix.os }} ${{ matrix.sanitize && 'ASAN' || '' }} (${{ matrix.config }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/checkout@v4 + with: + repository: wolfssl/wolfssl + ref: master + path: wolfssl + + - name: Install dependencies (Ubuntu) + if: runner.os == 'Linux' + run: | + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update + sudo apt-get install -y openssl + + - name: Install dependencies (macOS) + if: runner.os == 'macOS' + run: brew install automake libtool + + - name: Build and install wolfSSL + working-directory: ./wolfssl + run: | + ./autogen.sh + ./configure ${{ matrix.config }} ${{ matrix.sanitize }} --prefix=$GITHUB_WORKSPACE/build-dir + make -j + make install + + - name: Build wolfCLU + run: | + ./autogen.sh + ./configure ${{ matrix.sanitize }} --with-wolfssl=$GITHUB_WORKSPACE/build-dir + make -j + + - name: Run tests + env: + LD_LIBRARY_PATH: ${{ github.workspace }}/build-dir/lib + DYLD_LIBRARY_PATH: ${{ github.workspace }}/build-dir/lib + run: make check + + - name: Display log + if: always() + run: cat test-suite.log || true diff --git a/.github/workflows/fsanitize-check.yml b/.github/workflows/fsanitize-check.yml deleted file mode 100644 index 68b21c5b..00000000 --- a/.github/workflows/fsanitize-check.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: fsanitize check test - -on: - push: - branches: [ '*' ] - pull_request: - branches: [ '*' ] - -jobs: - build_wolfssl: - strategy: - fail-fast: false - matrix: - os: [ ubuntu-latest ] - config: [ - # Add new configs here and make wolfclu matrix match - '--enable-wolfclu', - '--enable-wolfclu --enable-crl --enable-dsa --enable-pkcs7', - '--enable-wolfclu --enable-smallstack', - '--enable-wolfclu --enable-experimental --enable-dilithium', - '--enable-wolfclu --enable-smallstack --enable-experimental --enable-dilithium', - '--enable-all', - ] - name: Build wolfssl - runs-on: ${{ matrix.os }} - timeout-minutes: 4 - steps: - - name: Checking cache for wolfssl - uses: actions/cache@v4 - id: cache-wolfssl - with: - path: build-dir/ - key: wolfclu-fsanitize-check-wolfssl-${{ strategy.job-index }}-${{ matrix.os }} - lookup-only: true - - - name: Checkout, build, and install wolfssl - if: steps.cache-wolfssl.outputs.cache-hit != 'true' - uses: wolfSSL/actions-build-autotools-project@v1 - with: - repository: wolfssl/wolfssl - ref: master - path: wolfssl - configure: ${{ matrix.config }} CC="gcc -fsanitize=address" - check: false - install: true - - build_wolfclu: - needs: build_wolfssl - strategy: - fail-fast: false - matrix: - os: [ ubuntu-latest ] - config: [ - '--enable-wolfclu', - '--enable-wolfclu --enable-crl --enable-dsa --enable-pkcs7', - '--enable-wolfclu --enable-smallstack', - '--enable-wolfclu --enable-experimental --enable-dilithium', - '--enable-wolfclu --enable-smallstack --enable-experimental --enable-dilithium', - '--enable-all', - ] - name: Build wolfclu - runs-on: ${{ matrix.os }} - timeout-minutes: 4 - steps: - - name: Install dependencies - run: | - # Don't prompt for anything - export DEBIAN_FRONTEND=noninteractive - sudo apt-get update - # openssl used for ocsp interop testing - sudo apt-get install -y openssl - - - name: Checking cache for wolfssl - uses: actions/cache@v4 - with: - path: build-dir/ - key: wolfclu-fsanitize-check-wolfssl-${{ strategy.job-index }}-${{ matrix.os }} - fail-on-cache-miss: true - - - name: Checkout, build, and test wolfclu - uses: wolfSSL/actions-build-autotools-project@v1 - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/build-dir/lib - with: - path: wolfclu - configure: CC="gcc -fsanitize=address" LDFLAGS="-L${{ github.workspace }}/build-dir/lib" CPPFLAGS="-I${{ github.workspace }}/build-dir/include" - check: true - - name: display log - if: always() - run: if [ -f test-suite.log ]; then cat test-suite.log; else echo "No test log"; fi diff --git a/.github/workflows/macos-check.yml b/.github/workflows/macos-check.yml deleted file mode 100644 index 4840e577..00000000 --- a/.github/workflows/macos-check.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: macOS Build Test - -on: - push: - branches: [ '*' ] - pull_request: - branches: [ '*' ] - -jobs: - build: - - runs-on: macos-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@master - - uses: actions/checkout@master - with: - repository: wolfssl/wolfssl - path: wolfssl_src - - name: brew - run: brew install automake libtool - - - name: wolfssl autogen - working-directory: ./wolfssl_src - run: ./autogen.sh - - name: wolfssl configure - working-directory: ./wolfssl_src - run: ./configure --enable-wolfclu --enable-crl --enable-dsa --enable-pkcs7 --prefix=$GITHUB_WORKSPACE/build-dir - - name: wolfssl make - working-directory: ./wolfssl_src - run: make - - name: wolfssl make install - working-directory: ./wolfssl_src - run: make install - - - name: Check wolfSSL install dir - run: ls $GITHUB_WORKSPACE/build-dir - - - name: autogen - run: ./autogen.sh - - name: configure - run: ./configure --with-wolfssl=$GITHUB_WORKSPACE/build-dir - - name: make - run: make - - name: make check - run: make check - - name: display log - if: always() - run: cat test-suite.log diff --git a/.github/workflows/ubuntu-check.yml b/.github/workflows/ubuntu-check.yml deleted file mode 100644 index 88912934..00000000 --- a/.github/workflows/ubuntu-check.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Ubuntu Build Test - -on: - push: - branches: [ '*' ] - pull_request: - branches: [ '*' ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - name: Install dependencies - run: | - # Don't prompt for anything - export DEBIAN_FRONTEND=noninteractive - sudo apt-get update - # openssl used for ocsp interop testing - sudo apt-get install -y openssl - - uses: actions/checkout@master - with: - repository: wolfssl/wolfssl - path: wolfssl - - name: wolfssl autogen - working-directory: ./wolfssl - run: ./autogen.sh - - name: wolfssl configure - working-directory: ./wolfssl - run: ./configure --enable-wolfclu --enable-crl --enable-dsa --enable-pkcs7 - - name: wolfssl make - working-directory: ./wolfssl - run: make - - name: wolfssl make install - working-directory: ./wolfssl - run: sudo make install - - name: ldconfig - working-directory: ./wolfssl - run: sudo ldconfig - - uses: actions/checkout@master - - name: autogen - run: ./autogen.sh - - name: configure - run: ./configure - - name: make - run: make - - name: make check - run: make check - - name: display log - if: always() - run: cat test-suite.log diff --git a/.github/workflows/windows-check.yml b/.github/workflows/windows-check.yml index d4891461..14b1b147 100644 --- a/.github/workflows/windows-check.yml +++ b/.github/workflows/windows-check.yml @@ -27,17 +27,17 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: repository: wolfssl/wolfssl path: wolfssl - - uses: actions/checkout@master + - uses: actions/checkout@v4 with: path: wolfclu - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1 + uses: microsoft/setup-msbuild@v2 - name: Restore wolfSSL NuGet packages working-directory: ${{env.GITHUB_WORKSPACE}} From f48af1af8f41101e2f283eecb2f75fa791a7630f Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 1 Apr 2026 15:12:04 +0200 Subject: [PATCH 08/17] Clean up Windows CI workflow and VS project files Simplify windows-check.yml: remove ${{env.GITHUB_WORKSPACE}} indirection, drop PlatformToolset/WindowsTargetPlatformVersion overrides, add timeout, pin wolfSSL ref, consolidate env vars. Update VS project to v145 toolset, add missing source files to filters, enable multi-processor compilation. --- .github/workflows/windows-check.yml | 92 ++++++++++++----------------- wolfCLU.vcxproj | 13 ++-- wolfCLU.vcxproj.filters | 26 +++++++- wolfclu.sln | 4 +- 4 files changed, 73 insertions(+), 62 deletions(-) diff --git a/.github/workflows/windows-check.yml b/.github/workflows/windows-check.yml index 14b1b147..ae6259a9 100644 --- a/.github/workflows/windows-check.yml +++ b/.github/workflows/windows-check.yml @@ -2,67 +2,51 @@ name: Windows Build Test on: push: - branches: [ '*' ] + branches: ['*'] pull_request: - branches: [ '*' ] - -env: - # Path to the solution file relative to the root of the project. - WOLFSSL_SOLUTION_FILE_PATH: wolfssl/wolfssl64.sln - SOLUTION_FILE_PATH: wolfclu.sln - USER_SETTINGS_H_NEW: wolfclu/ide/winvs/user_settings.h - USER_SETTINGS_H: wolfssl/IDE/WIN/user_settings.h - INCLUDE_DIR: wolfclu - - # Configuration type to build. - # You can convert this to a build matrix if you need coverage of multiple configuration types. - # https://docs.github.com/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - WOLFSSL_BUILD_CONFIGURATION: Release - WOLFCLU_BUILD_CONFIGURATION: Release - BUILD_PLATFORM: x64 - TARGET_PLATFORM: 10 + branches: ['*'] jobs: build: runs-on: windows-latest + timeout-minutes: 10 + + env: + BUILD_PLATFORM: x64 + BUILD_CONFIGURATION: Release steps: - - uses: actions/checkout@v4 - with: - repository: wolfssl/wolfssl - path: wolfssl - - - uses: actions/checkout@v4 - with: - path: wolfclu - - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v2 + - uses: actions/checkout@v4 + with: + repository: wolfssl/wolfssl + ref: master + path: wolfssl + + - uses: actions/checkout@v4 + with: + path: wolfclu + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v2 + + - name: Restore wolfSSL NuGet packages + run: nuget restore wolfssl/wolfssl64.sln + + - name: Replace user_settings.h + run: cp wolfclu/ide/winvs/user_settings.h wolfssl/IDE/WIN/user_settings.h + + - name: Build wolfSSL + run: msbuild /m /p:Platform=${{ env.BUILD_PLATFORM }} /p:Configuration=${{ env.BUILD_CONFIGURATION }} wolfssl/wolfssl64.sln + + - name: Restore wolfCLU NuGet packages + working-directory: wolfclu + run: nuget restore wolfclu.sln - - name: Restore wolfSSL NuGet packages - working-directory: ${{env.GITHUB_WORKSPACE}} - run: nuget restore ${{env.WOLFSSL_SOLUTION_FILE_PATH}} - - - name: replace with wolfCLU user_settings.h - working-directory: ${{env.GITHUB_WORKSPACE}} - run: cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} - - - name: Build wolfssl - working-directory: ${{env.GITHUB_WORKSPACE}} - # Add additional options to the MSBuild command line here (like platform or verbosity level). - # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference - run: msbuild /m /p:PlatformToolset=v142 /p:Platform=${{env.BUILD_PLATFORM}} /p:WindowsTargetPlatformVersion=${{env.TARGET_PLATFORM}} /p:Configuration=${{env.WOLFSSL_BUILD_CONFIGURATION}} ${{env.WOLFSSL_SOLUTION_FILE_PATH}} + - name: Build wolfCLU + working-directory: wolfclu + run: msbuild /m /p:Platform=${{ env.BUILD_PLATFORM }} /p:Configuration=${{ env.BUILD_CONFIGURATION }} wolfclu.sln - - name: Restore NuGet packages - working-directory: ${{env.GITHUB_WORKSPACE}}wolfclu - run: nuget restore ${{env.SOLUTION_FILE_PATH}} - - - name: Build - working-directory: ${{env.GITHUB_WORKSPACE}}wolfclu - # Add additional options to the MSBuild command line here (like platform or verbosity level). - # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference - run: msbuild /m /p:PlatformToolset=v142 /p:Platform=${{env.BUILD_PLATFORM}} /p:WindowsTargetPlatformVersion=${{env.TARGET_PLATFORM}} /p:Configuration=${{env.WOLFCLU_BUILD_CONFIGURATION}} ${{env.SOLUTION_FILE_PATH}} + - name: Run tests + working-directory: wolfclu + run: python tests/run_tests.py - - name: Run tests - working-directory: ${{env.GITHUB_WORKSPACE}}wolfclu - run: python tests/run_tests.py diff --git a/wolfCLU.vcxproj b/wolfCLU.vcxproj index a6017fa3..1cb772c2 100644 --- a/wolfCLU.vcxproj +++ b/wolfCLU.vcxproj @@ -28,22 +28,22 @@ Application true - v143 + v145 Application false - v143 + v145 Application true - v143 + v145 Application false - v143 + v145 @@ -89,6 +89,7 @@ Level3 ProgramDatabase Disabled + true MachineX86 @@ -120,6 +121,7 @@ $(ProjectDir);.;./../wolfssl/;../wolfssl/IDE/WIN;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;WOLFCLU_EXPORTS;WOLFSSL_LIB;_WINDLL;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions) + true Ws2_32.lib;wolfssl.lib;%(AdditionalDependencies) @@ -131,6 +133,7 @@ $(ProjectDir);.;./../wolfssl/;../wolfssl/IDE/WIN;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;WOLFCLU_EXPORTS;WOLFSSL_LIB;_WINDLL;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions) + true Ws2_32.lib;wolfssl.lib;%(AdditionalDependencies) @@ -210,4 +213,4 @@ - + \ No newline at end of file diff --git a/wolfCLU.vcxproj.filters b/wolfCLU.vcxproj.filters index 2b49eae0..de176b32 100644 --- a/wolfCLU.vcxproj.filters +++ b/wolfCLU.vcxproj.filters @@ -132,6 +132,30 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -189,4 +213,4 @@ Header Files - + \ No newline at end of file diff --git a/wolfclu.sln b/wolfclu.sln index a3c44ee0..9dcba3d3 100644 --- a/wolfclu.sln +++ b/wolfclu.sln @@ -1,7 +1,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32014.148 +# Visual Studio Version 18 +VisualStudioVersion = 18.4.11620.152 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wolfCLU", "wolfCLU.vcxproj", "{CFC6FB69-7DA4-4E35-851E-776010E92FB3}" EndProject From 28939d36f005dd9aeb2aba551d5685685057c43c Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 1 Apr 2026 15:29:36 +0200 Subject: [PATCH 09/17] Use vswhere to detect PlatformToolset --- .github/workflows/windows-check.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/windows-check.yml b/.github/workflows/windows-check.yml index ae6259a9..fcb3c6ad 100644 --- a/.github/workflows/windows-check.yml +++ b/.github/workflows/windows-check.yml @@ -29,22 +29,22 @@ jobs: - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@v2 - - name: Restore wolfSSL NuGet packages - run: nuget restore wolfssl/wolfssl64.sln - - name: Replace user_settings.h run: cp wolfclu/ide/winvs/user_settings.h wolfssl/IDE/WIN/user_settings.h - - name: Build wolfSSL - run: msbuild /m /p:Platform=${{ env.BUILD_PLATFORM }} /p:Configuration=${{ env.BUILD_CONFIGURATION }} wolfssl/wolfssl64.sln + - name: Detect platform toolset + id: toolset + run: | + $major = (vswhere -latest -property installationVersion).Split('.')[0] + $ts = @{ "16" = "v142"; "17" = "v143"; "18" = "v144" }[$major] + echo "value=$ts" >> $env:GITHUB_OUTPUT - - name: Restore wolfCLU NuGet packages - working-directory: wolfclu - run: nuget restore wolfclu.sln + - name: Build wolfSSL + run: msbuild /m /p:PlatformToolset=${{ steps.toolset.outputs.value }} /p:Platform=${{ env.BUILD_PLATFORM }} /p:Configuration=${{ env.BUILD_CONFIGURATION }} wolfssl/wolfssl64.sln - name: Build wolfCLU working-directory: wolfclu - run: msbuild /m /p:Platform=${{ env.BUILD_PLATFORM }} /p:Configuration=${{ env.BUILD_CONFIGURATION }} wolfclu.sln + run: msbuild /m /p:PlatformToolset=${{ steps.toolset.outputs.value }} /p:Platform=${{ env.BUILD_PLATFORM }} /p:Configuration=${{ env.BUILD_CONFIGURATION }} wolfclu.sln - name: Run tests working-directory: wolfclu From 484785581a66f631e14f17c1bc8094d8b4c25c01 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 1 Apr 2026 17:17:47 +0200 Subject: [PATCH 10/17] Fix test failures --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a127d8e7..23ccd2f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + path: wolfclu - uses: actions/checkout@v4 with: @@ -54,12 +56,14 @@ jobs: make install - name: Build wolfCLU + working-directory: ./wolfclu run: | ./autogen.sh ./configure ${{ matrix.sanitize }} --with-wolfssl=$GITHUB_WORKSPACE/build-dir make -j - name: Run tests + working-directory: ./wolfclu env: LD_LIBRARY_PATH: ${{ github.workspace }}/build-dir/lib DYLD_LIBRARY_PATH: ${{ github.workspace }}/build-dir/lib @@ -67,4 +71,5 @@ jobs: - name: Display log if: always() + working-directory: ./wolfclu run: cat test-suite.log || true From f5d97d0c9a3f91c692e4c7e718944df774e2c307 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 1 Apr 2026 17:36:46 +0200 Subject: [PATCH 11/17] Address code review --- tests/hash/hash-test.py | 6 ++++++ tests/ocsp/ocsp-test.py | 2 +- tests/run_tests.py | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/hash/hash-test.py b/tests/hash/hash-test.py index 9c63a025..d36ee4d3 100644 --- a/tests/hash/hash-test.py +++ b/tests/hash/hash-test.py @@ -61,6 +61,12 @@ def setUpClass(cls): if not os.path.isdir(CERTS_DIR): raise unittest.SkipTest("certs directory not found") + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + def test_md5(self): r = run_wolfssl("md5", CERT_FILE) self.assertEqual(r.returncode, 0, r.stderr) diff --git a/tests/ocsp/ocsp-test.py b/tests/ocsp/ocsp-test.py index 6fe89077..d1d8b64e 100644 --- a/tests/ocsp/ocsp-test.py +++ b/tests/ocsp/ocsp-test.py @@ -38,7 +38,7 @@ def _ocsp_supported(binary): try: r = subprocess.run([binary, "ocsp", "-help"], capture_output=True, timeout=5) - return True + return r.returncode == 0 except (FileNotFoundError, subprocess.TimeoutExpired): return False diff --git a/tests/run_tests.py b/tests/run_tests.py index befc2409..7ffb1cb7 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -36,7 +36,10 @@ def main(): for test_file in sorted(glob.glob(pattern, recursive=True)): suite.addTests(load_tests_from_file(test_file)) - runner = unittest.TextTestRunner(verbosity=2, durations=5) + kwargs = dict(verbosity=2) + if sys.version_info >= (3, 12): + kwargs["durations"] = 5 + runner = unittest.TextTestRunner(**kwargs) result = runner.run(suite) sys.exit(0 if result.wasSuccessful() else 1) From e7e1d5d14a63b7526e1afc23365a70da503d441a Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 2 Apr 2026 10:21:53 +0200 Subject: [PATCH 12/17] Address PR review comments - Use $(DefaultPlatformToolset) in vcxproj instead of hard-coded v145 - Remove toolset detection step from Windows CI (no longer needed) - Disable Python tests when Python 3 not found instead of failing build - Add -noservername flag to s_client (SNI on by default, matching openssl) - Fix wolfclu_test.py paths to be __file__-relative for location independence - Add trailing newline to pkcs12 stdin password inputs - Redirect s_server stdout/stderr to DEVNULL to prevent pipe deadlock --- .github/workflows/windows-check.yml | 9 +++++++-- Makefile.am | 2 ++ configure.ac | 1 + src/client/clu_client_setup.c | 14 +++++++++++--- tests/pkcs/pkcs12-test.py | 6 +++--- tests/server/server-test.py | 4 +--- tests/wolfclu_test.py | 17 ++++++++++------- wolfCLU.vcxproj | 8 ++++---- wolfclu/clu_optargs.h | 1 + 9 files changed, 40 insertions(+), 22 deletions(-) diff --git a/.github/workflows/windows-check.yml b/.github/workflows/windows-check.yml index fcb3c6ad..05f6b035 100644 --- a/.github/workflows/windows-check.yml +++ b/.github/workflows/windows-check.yml @@ -36,7 +36,12 @@ jobs: id: toolset run: | $major = (vswhere -latest -property installationVersion).Split('.')[0] - $ts = @{ "16" = "v142"; "17" = "v143"; "18" = "v144" }[$major] + $toolsets = @{ "16" = "v142"; "17" = "v143"; "18" = "v145" } + $ts = $toolsets[$major] + if (-not $ts) { + Write-Error "Unsupported Visual Studio major version '$major'." + exit 1 + } echo "value=$ts" >> $env:GITHUB_OUTPUT - name: Build wolfSSL @@ -44,7 +49,7 @@ jobs: - name: Build wolfCLU working-directory: wolfclu - run: msbuild /m /p:PlatformToolset=${{ steps.toolset.outputs.value }} /p:Platform=${{ env.BUILD_PLATFORM }} /p:Configuration=${{ env.BUILD_CONFIGURATION }} wolfclu.sln + run: msbuild /m /p:Platform=${{ env.BUILD_PLATFORM }} /p:Configuration=${{ env.BUILD_CONFIGURATION }} wolfclu.sln - name: Run tests working-directory: wolfclu diff --git a/Makefile.am b/Makefile.am index 965bbdc4..d27515b8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -48,6 +48,7 @@ man_MANS+= manpages/wolfssl.1 include src/include.am include wolfclu/include.am +if HAVE_PYTHON include tests/dh/include.am include tests/dsa/include.am include tests/pkey/include.am @@ -65,6 +66,7 @@ include tests/bench/include.am include tests/client/include.am include tests/server/include.am include tests/testEncDec/include.am +endif include ide/include.am #####include data/include.am diff --git a/configure.ac b/configure.ac index f3b10e38..576601d0 100644 --- a/configure.ac +++ b/configure.ac @@ -57,6 +57,7 @@ AC_PROG_MAKE_SET AM_PROG_CC_C_O AM_PATH_PYTHON([3.0],, [:]) AC_SUBST([PYTHON]) +AM_CONDITIONAL([HAVE_PYTHON], [test "$PYTHON" != ":"]) # Checks for headers/libraries AC_CHECK_HEADERS([sys/time.h string.h termios.h unistd.h]) diff --git a/src/client/clu_client_setup.c b/src/client/clu_client_setup.c index 3be36370..1f2cfd8b 100644 --- a/src/client/clu_client_setup.c +++ b/src/client/clu_client_setup.c @@ -35,6 +35,7 @@ static const struct option client_options[] = { {"-CAfile", required_argument, 0, WOLFCLU_CAFILE }, {"-verify_return_error", no_argument, 0, WOLFCLU_VERIFY_RETURN_ERROR}, {"-disable_stdin_check", no_argument, 0, WOLFCLU_DISABLE_STDINCHK }, + {"-noservername", no_argument, 0, WOLFCLU_NOSERVERNAME }, {"-help", no_argument, 0, WOLFCLU_HELP }, {"-h", no_argument, 0, WOLFCLU_HELP }, @@ -54,7 +55,8 @@ static void wolfCLU_ClientHelp(void) WOLFCLU_LOG(WOLFCLU_L0, "\t-starttls "); WOLFCLU_LOG(WOLFCLU_L0, "\t-CAfile "); WOLFCLU_LOG(WOLFCLU_L0, "\t-verify_return_error close connection on verification error"); - WOLFCLU_LOG(WOLFCLU_L0, "\t-disable_stdin_check ") + WOLFCLU_LOG(WOLFCLU_L0, "\t-disable_stdin_check "); + WOLFCLU_LOG(WOLFCLU_L0, "\t-noservername do not send Server Name Indication"); } static const char hostFlag[] = "-h"; @@ -100,6 +102,7 @@ int wolfCLU_Client(int argc, char** argv) int idx = 0; /* Don't verify peer by default (same as OpenSSL). */ int verify = 0; + int noservername = 0; char* ipv6 = NULL; int clientArgc = 0; @@ -195,8 +198,9 @@ int wolfCLU_Client(int argc, char** argv) } } - /* Set SNI hostname so modern servers accept the connection */ - if (ret == WOLFCLU_SUCCESS && host != NULL) { + /* Set SNI hostname so modern servers accept the connection. + * Matches openssl s_client default; use -noservername to disable. */ + if (ret == WOLFCLU_SUCCESS && host != NULL && !noservername) { ret = _addClientArg(clientArgv, sniFlag, &clientArgc); if (ret == WOLFCLU_SUCCESS) { ret = _addClientArg(clientArgv, host, &clientArgc); @@ -234,6 +238,10 @@ int wolfCLU_Client(int argc, char** argv) &clientArgc); } break; + + case WOLFCLU_NOSERVERNAME: + noservername = 1; + break; case WOLFCLU_HELP: wolfCLU_ClientHelp(); return WOLFCLU_SUCCESS; diff --git a/tests/pkcs/pkcs12-test.py b/tests/pkcs/pkcs12-test.py index c162f9de..3497c5ba 100644 --- a/tests/pkcs/pkcs12-test.py +++ b/tests/pkcs/pkcs12-test.py @@ -40,7 +40,7 @@ def test_nocerts(self): r = subprocess.run( [WOLFSSL_BIN, "pkcs12", "-nodes", "-nocerts", "-passin", "stdin", "-passout", "pass:", "-in", P12_FILE], - input=b"wolfSSL test", capture_output=True, text=False, + input=b"wolfSSL test\n", capture_output=True, text=False, timeout=60, ) self.assertEqual(r.returncode, 0, r.stderr) @@ -50,7 +50,7 @@ def test_nokeys(self): r = subprocess.run( [WOLFSSL_BIN, "pkcs12", "-nokeys", "-passin", "stdin", "-passout", "pass:", "-in", P12_FILE], - input=b"wolfSSL test", capture_output=True, text=False, + input=b"wolfSSL test\n", capture_output=True, text=False, timeout=60, ) self.assertEqual(r.returncode, 0, r.stderr) @@ -65,7 +65,7 @@ def test_nocerts_with_passout(self): r = subprocess.run( [WOLFSSL_BIN, "pkcs12", "-passin", "stdin", "-passout", "pass:", "-in", P12_FILE, "-nocerts"], - input=b"wolfSSL test", capture_output=True, text=False, + input=b"wolfSSL test\n", capture_output=True, text=False, timeout=60, ) self.assertEqual(r.returncode, 0, r.stderr) diff --git a/tests/server/server-test.py b/tests/server/server-test.py index 7e3cb44f..2507d607 100644 --- a/tests/server/server-test.py +++ b/tests/server/server-test.py @@ -36,7 +36,7 @@ def test_server_client(self): "-key", os.path.join(CERTS_DIR, "server-key.pem"), "-cert", os.path.join(CERTS_DIR, "server-cert.pem"), "-noVerify", "-readyFile", readyfile], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, ) @@ -64,8 +64,6 @@ def test_server_client(self): finally: server.terminate() server.wait(timeout=5) - server.stdout.close() - server.stderr.close() if __name__ == "__main__": diff --git a/tests/wolfclu_test.py b/tests/wolfclu_test.py index a92b3653..170c4794 100644 --- a/tests/wolfclu_test.py +++ b/tests/wolfclu_test.py @@ -4,20 +4,23 @@ import platform import subprocess +_TESTS_DIR = os.path.dirname(os.path.abspath(__file__)) +_PROJECT_ROOT = os.path.dirname(_TESTS_DIR) + def _find_wolfssl_bin(): """Locate the wolfssl binary, searching common build output paths.""" if platform.system() == "Windows": candidates = [ - os.path.join(".", "x64", "Debug", "wolfssl.exe"), - os.path.join(".", "x64", "Release", "wolfssl.exe"), - os.path.join(".", "Debug", "wolfssl.exe"), - os.path.join(".", "Release", "wolfssl.exe"), - os.path.join(".", "wolfssl.exe"), + os.path.join(_PROJECT_ROOT, "x64", "Debug", "wolfssl.exe"), + os.path.join(_PROJECT_ROOT, "x64", "Release", "wolfssl.exe"), + os.path.join(_PROJECT_ROOT, "Debug", "wolfssl.exe"), + os.path.join(_PROJECT_ROOT, "Release", "wolfssl.exe"), + os.path.join(_PROJECT_ROOT, "wolfssl.exe"), ] else: candidates = [ - os.path.join(".", "wolfssl"), + os.path.join(_PROJECT_ROOT, "wolfssl"), ] for path in candidates: @@ -29,7 +32,7 @@ def _find_wolfssl_bin(): WOLFSSL_BIN = _find_wolfssl_bin() -CERTS_DIR = os.path.join(".", "certs") +CERTS_DIR = os.path.join(_PROJECT_ROOT, "certs") def run_wolfssl(*args, stdin_data=None, timeout=60): diff --git a/wolfCLU.vcxproj b/wolfCLU.vcxproj index 1cb772c2..468bea77 100644 --- a/wolfCLU.vcxproj +++ b/wolfCLU.vcxproj @@ -28,22 +28,22 @@ Application true - v145 + $(DefaultPlatformToolset) Application false - v145 + $(DefaultPlatformToolset) Application true - v145 + $(DefaultPlatformToolset) Application false - v145 + $(DefaultPlatformToolset) diff --git a/wolfclu/clu_optargs.h b/wolfclu/clu_optargs.h index b4bc91e0..13d9b150 100644 --- a/wolfclu/clu_optargs.h +++ b/wolfclu/clu_optargs.h @@ -114,6 +114,7 @@ enum { WOLFCLU_CHECK, WOLFCLU_VERIFY_RETURN_ERROR, WOLFCLU_DISABLE_STDINCHK, + WOLFCLU_NOSERVERNAME, WOLFCLU_NOCRYPT, WOLFCLU_TOPKCS8, }; From bcd37428ca3366ed4937760e9f04f3a9a8f46ccf Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 2 Apr 2026 11:10:15 +0200 Subject: [PATCH 13/17] Exit 77 for skipped Python tests so automake reports SKIP Add test_main() helper in wolfclu_test.py that runs unittest with exit=False and translates the result: all-skipped/no-tests -> exit 77 (automake SKIP), failures -> exit 1, otherwise -> exit 0. --- tests/base64/base64-test.py | 4 ++-- tests/bench/bench-test.py | 4 ++-- tests/client/client-test.py | 4 ++-- tests/dgst/dgst-test.py | 4 ++-- tests/dh/dh-test.py | 4 ++-- tests/dsa/dsa-test.py | 4 ++-- tests/encrypt/enc-test.py | 4 ++-- tests/genkey_sign_ver/genkey-sign-ver-test.py | 4 ++-- tests/hash/hash-test.py | 4 ++-- tests/ocsp-scgi/ocsp-scgi-test.py | 4 ++-- tests/ocsp/ocsp-test.py | 4 ++-- tests/pkcs/pkcs12-test.py | 4 ++-- tests/pkcs/pkcs7-test.py | 4 ++-- tests/pkcs/pkcs8-test.py | 4 ++-- tests/pkey/ecparam-test.py | 4 ++-- tests/pkey/pkey-test.py | 4 ++-- tests/pkey/rsa-test.py | 4 ++-- tests/rand/rand-test.py | 4 ++-- tests/server/server-test.py | 4 ++-- tests/testEncDec/encdec-test.py | 4 ++-- tests/wolfclu_test.py | 21 +++++++++++++++++++ tests/x509/CRL-verify-test.py | 4 ++-- tests/x509/x509-ca-test.py | 4 ++-- tests/x509/x509-process-test.py | 4 ++-- tests/x509/x509-req-test.py | 4 ++-- tests/x509/x509-verify-test.py | 4 ++-- 26 files changed, 71 insertions(+), 50 deletions(-) diff --git a/tests/base64/base64-test.py b/tests/base64/base64-test.py index 261bbc34..671ca8a3 100644 --- a/tests/base64/base64-test.py +++ b/tests/base64/base64-test.py @@ -9,7 +9,7 @@ # Allow importing the shared helper when run standalone or via the test runner sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main class Base64Test(unittest.TestCase): @@ -105,4 +105,4 @@ def test_stdin_input(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/bench/bench-test.py b/tests/bench/bench-test.py index b49791f9..6b27ac34 100644 --- a/tests/bench/bench-test.py +++ b/tests/bench/bench-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import run_wolfssl +from wolfclu_test import run_wolfssl, test_main class BenchTest(unittest.TestCase): @@ -25,4 +25,4 @@ def test_bench_md5(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/client/client-test.py b/tests/client/client-test.py index 52786115..d4cf89d8 100644 --- a/tests/client/client-test.py +++ b/tests/client/client-test.py @@ -7,7 +7,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main class ClientTest(unittest.TestCase): @@ -58,4 +58,4 @@ def test_s_client_x509(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/dgst/dgst-test.py b/tests/dgst/dgst-test.py index a83fae0b..e298be1b 100644 --- a/tests/dgst/dgst-test.py +++ b/tests/dgst/dgst-test.py @@ -7,7 +7,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, run_wolfssl +from wolfclu_test import CERTS_DIR, run_wolfssl, test_main DGST_DIR = os.path.join(".", "tests", "dgst") @@ -213,4 +213,4 @@ def test_ecc_sign_verify_roundtrip(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/dh/dh-test.py b/tests/dh/dh-test.py index 7c40e839..71cb6575 100644 --- a/tests/dh/dh-test.py +++ b/tests/dh/dh-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, run_wolfssl +from wolfclu_test import CERTS_DIR, run_wolfssl, test_main class DhParamTest(unittest.TestCase): @@ -108,4 +108,4 @@ def test_bad_input_fails(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/dsa/dsa-test.py b/tests/dsa/dsa-test.py index b8e0eb79..2f4b9c8d 100644 --- a/tests/dsa/dsa-test.py +++ b/tests/dsa/dsa-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, run_wolfssl +from wolfclu_test import CERTS_DIR, run_wolfssl, test_main class DsaParamTest(unittest.TestCase): @@ -95,4 +95,4 @@ def test_bad_input_fails(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/encrypt/enc-test.py b/tests/encrypt/enc-test.py index 9d02bd2f..46aad58d 100644 --- a/tests/encrypt/enc-test.py +++ b/tests/encrypt/enc-test.py @@ -9,7 +9,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, WOLFSSL_BIN, run_wolfssl +from wolfclu_test import CERTS_DIR, WOLFSSL_BIN, run_wolfssl, test_main def run_enc(*args, password=""): @@ -276,4 +276,4 @@ def test_legacy_aes_cbc_128_roundtrip(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/genkey_sign_ver/genkey-sign-ver-test.py b/tests/genkey_sign_ver/genkey-sign-ver-test.py index de176571..e7d2d0e4 100644 --- a/tests/genkey_sign_ver/genkey-sign-ver-test.py +++ b/tests/genkey_sign_ver/genkey-sign-ver-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, run_wolfssl +from wolfclu_test import WOLFSSL_BIN, run_wolfssl, test_main # Files that tests may create; cleaned up by tearDownClass _TEMP_FILES = [] @@ -250,4 +250,4 @@ def test_xmss_raw(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/hash/hash-test.py b/tests/hash/hash-test.py index d36ee4d3..2393efbf 100644 --- a/tests/hash/hash-test.py +++ b/tests/hash/hash-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, run_wolfssl +from wolfclu_test import CERTS_DIR, run_wolfssl, test_main HASH_DIR = os.path.join(".", "tests", "hash") CERT_FILE = os.path.join(CERTS_DIR, "ca-cert.pem") @@ -89,4 +89,4 @@ def test_sha512(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/ocsp-scgi/ocsp-scgi-test.py b/tests/ocsp-scgi/ocsp-scgi-test.py index 4646e354..309371b6 100644 --- a/tests/ocsp-scgi/ocsp-scgi-test.py +++ b/tests/ocsp-scgi/ocsp-scgi-test.py @@ -21,7 +21,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, test_main HAS_OPENSSL = shutil.which("openssl") is not None @@ -288,4 +288,4 @@ def test_08_graceful_shutdown(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/ocsp/ocsp-test.py b/tests/ocsp/ocsp-test.py index d1d8b64e..01384071 100644 --- a/tests/ocsp/ocsp-test.py +++ b/tests/ocsp/ocsp-test.py @@ -15,7 +15,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, test_main HAS_OPENSSL = shutil.which("openssl") is not None OCSP_PORT_BASE = 6960 @@ -335,4 +335,4 @@ class TestOpensslClientOpensslResponder(_OCSPInteropBase): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/pkcs/pkcs12-test.py b/tests/pkcs/pkcs12-test.py index 3497c5ba..1e099b1b 100644 --- a/tests/pkcs/pkcs12-test.py +++ b/tests/pkcs/pkcs12-test.py @@ -7,7 +7,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main P12_FILE = os.path.join(CERTS_DIR, "test-servercert.p12") @@ -72,4 +72,4 @@ def test_nocerts_with_passout(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/pkcs/pkcs7-test.py b/tests/pkcs/pkcs7-test.py index 9c01bc6d..bc82156e 100644 --- a/tests/pkcs/pkcs7-test.py +++ b/tests/pkcs/pkcs7-test.py @@ -7,7 +7,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main class Pkcs7Test(unittest.TestCase): @@ -69,4 +69,4 @@ def test_stdin_input(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/pkcs/pkcs8-test.py b/tests/pkcs/pkcs8-test.py index 70f20e9f..12e7e87b 100644 --- a/tests/pkcs/pkcs8-test.py +++ b/tests/pkcs/pkcs8-test.py @@ -8,7 +8,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main def _is_fips(): @@ -113,4 +113,4 @@ def test_fail_wrong_format(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/pkey/ecparam-test.py b/tests/pkey/ecparam-test.py index ef1f9478..037cb39f 100644 --- a/tests/pkey/ecparam-test.py +++ b/tests/pkey/ecparam-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, run_wolfssl +from wolfclu_test import CERTS_DIR, run_wolfssl, test_main def _get_curve_names(): @@ -162,4 +162,4 @@ def test_all_curves_genkey(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/pkey/pkey-test.py b/tests/pkey/pkey-test.py index e88021f5..d8e50ce6 100644 --- a/tests/pkey/pkey-test.py +++ b/tests/pkey/pkey-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, run_wolfssl +from wolfclu_test import CERTS_DIR, run_wolfssl, test_main ECC_PUBKEY_PEM = """\ -----BEGIN PUBLIC KEY----- @@ -85,4 +85,4 @@ def test_pem_der_pem_public(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/pkey/rsa-test.py b/tests/pkey/rsa-test.py index 8ba50907..72d966b4 100644 --- a/tests/pkey/rsa-test.py +++ b/tests/pkey/rsa-test.py @@ -7,7 +7,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, run_wolfssl +from wolfclu_test import CERTS_DIR, run_wolfssl, test_main RSA_PUBKEY_PEM = """\ -----BEGIN PUBLIC KEY----- @@ -140,4 +140,4 @@ def test_modulus_noout(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/rand/rand-test.py b/tests/rand/rand-test.py index 455fa46e..84843fba 100644 --- a/tests/rand/rand-test.py +++ b/tests/rand/rand-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import run_wolfssl +from wolfclu_test import run_wolfssl, test_main class RandTest(unittest.TestCase): @@ -44,4 +44,4 @@ def test_output_file(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/server/server-test.py b/tests/server/server-test.py index 2507d607..4f22f674 100644 --- a/tests/server/server-test.py +++ b/tests/server/server-test.py @@ -8,7 +8,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, test_main class ServerClientTest(unittest.TestCase): @@ -67,4 +67,4 @@ def test_server_client(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/testEncDec/encdec-test.py b/tests/testEncDec/encdec-test.py index e896dbae..435c6c1b 100644 --- a/tests/testEncDec/encdec-test.py +++ b/tests/testEncDec/encdec-test.py @@ -7,7 +7,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import run_wolfssl +from wolfclu_test import run_wolfssl, test_main # Small test input — created once, used by all tests INPUT_FILE = "encdec_input.txt" @@ -120,4 +120,4 @@ def test_camellia_cbc_256(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/wolfclu_test.py b/tests/wolfclu_test.py index 170c4794..fc9e6a44 100644 --- a/tests/wolfclu_test.py +++ b/tests/wolfclu_test.py @@ -3,6 +3,8 @@ import os import platform import subprocess +import sys +import unittest _TESTS_DIR = os.path.dirname(os.path.abspath(__file__)) _PROJECT_ROOT = os.path.dirname(_TESTS_DIR) @@ -49,3 +51,22 @@ def run_wolfssl(*args, stdin_data=None, timeout=60): else: kwargs["stdin"] = subprocess.DEVNULL return subprocess.run(cmd, **kwargs) + + +def test_main(): + """Run tests with automake-compatible exit codes. + + Automake interprets exit 77 as SKIP. Python's unittest exits 0 even + when every test was skipped, so automake would report PASS. This + wrapper runs unittest with exit=False and translates the result: + - failures/errors -> exit 1 + - all skipped / no tests run -> exit 77 (automake SKIP) + - otherwise -> exit 0 (automake PASS) + """ + prog = unittest.main(module='__main__', exit=False) + result = prog.result + if not result.wasSuccessful(): + sys.exit(1) + if result.testsRun == 0: + sys.exit(77) + sys.exit(0) diff --git a/tests/x509/CRL-verify-test.py b/tests/x509/CRL-verify-test.py index 5cbb9111..abe01d5e 100644 --- a/tests/x509/CRL-verify-test.py +++ b/tests/x509/CRL-verify-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, run_wolfssl +from wolfclu_test import CERTS_DIR, run_wolfssl, test_main def _has_crl(): @@ -161,4 +161,4 @@ def test_crl_text_noout(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/x509/x509-ca-test.py b/tests/x509/x509-ca-test.py index b06abe7f..ca404794 100644 --- a/tests/x509/x509-ca-test.py +++ b/tests/x509/x509-ca-test.py @@ -7,7 +7,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main _SKIP_WIN = sys.platform == "win32" _WIN_REASON = "CA config file paths not supported on Windows UNC shares" @@ -748,4 +748,4 @@ def test_relative_out_path(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/x509/x509-process-test.py b/tests/x509/x509-process-test.py index 092ba638..0ad428e1 100644 --- a/tests/x509/x509-process-test.py +++ b/tests/x509/x509-process-test.py @@ -8,7 +8,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main TESTS_X509_DIR = os.path.join(".", "tests", "x509") HAS_OPENSSL = shutil.which("openssl") is not None @@ -505,4 +505,4 @@ def test_4f_nonexistent_file_pem(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/x509/x509-req-test.py b/tests/x509/x509-req-test.py index 838df890..9d5301bb 100644 --- a/tests/x509/x509-req-test.py +++ b/tests/x509/x509-req-test.py @@ -8,7 +8,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl +from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main HAS_OPENSSL = shutil.which("openssl") is not None _SKIP_WIN = sys.platform == "win32" @@ -726,4 +726,4 @@ def test_csr_version_openssl_interop(self): if __name__ == "__main__": - unittest.main() + test_main() diff --git a/tests/x509/x509-verify-test.py b/tests/x509/x509-verify-test.py index 1a40a97a..4f42be6c 100644 --- a/tests/x509/x509-verify-test.py +++ b/tests/x509/x509-verify-test.py @@ -6,7 +6,7 @@ import unittest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from wolfclu_test import CERTS_DIR, run_wolfssl +from wolfclu_test import CERTS_DIR, run_wolfssl, test_main def _has_crl(): @@ -152,4 +152,4 @@ def test_untrusted_chain(self): if __name__ == "__main__": - unittest.main() + test_main() From 85be9ff045cb4aff4378f839304268cd922fec01 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 2 Apr 2026 18:53:28 +0200 Subject: [PATCH 14/17] Fix Camellia key size in encrypt/decrypt wc_CamelliaSetKey was passed `block` (cipher block size, always 16) instead of `size / 8` (key size in bytes). The `size` parameter is in bits (128/192/256) so it must be divided by 8. The old code happened to work for camellia-128 since block == 128/8 == 16. --- src/crypto/clu_decrypt.c | 2 +- src/crypto/clu_encrypt.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/clu_decrypt.c b/src/crypto/clu_decrypt.c index 997d85ed..5eb2fc1c 100644 --- a/src/crypto/clu_decrypt.c +++ b/src/crypto/clu_decrypt.c @@ -172,7 +172,7 @@ int wolfCLU_decrypt(int alg, char* mode, byte* pwdKey, byte* key, int size, (alg == WOLFCLU_CAMELLIA128CBC || alg == WOLFCLU_CAMELLIA192CBC || alg == WOLFCLU_CAMELLIA256CBC)) { - ret = wc_CamelliaSetKey(&camellia, key, block, iv); + ret = wc_CamelliaSetKey(&camellia, key, size / 8, iv); if (ret == 0) { wc_CamelliaCbcDecrypt(&camellia, output, input, tempMax); } diff --git a/src/crypto/clu_encrypt.c b/src/crypto/clu_encrypt.c index 2601ca6c..750a6ca8 100644 --- a/src/crypto/clu_encrypt.c +++ b/src/crypto/clu_encrypt.c @@ -218,7 +218,7 @@ int wolfCLU_encrypt(int alg, char* mode, byte* pwdKey, byte* key, int size, #ifdef HAVE_CAMELLIA if (alg == WOLFCLU_CAMELLIA128CBC || alg == WOLFCLU_CAMELLIA192CBC || alg == WOLFCLU_CAMELLIA256CBC) { - ret = wc_CamelliaSetKey(&camellia, key, block, iv); + ret = wc_CamelliaSetKey(&camellia, key, size / 8, iv); if (ret != 0) { XFCLOSE(inFile); wolfCLU_LogError("CamelliaSetKey failed."); From ad92c2304fbd16cbfdc605ba7f349a0239777ed6 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 2 Apr 2026 19:26:23 +0200 Subject: [PATCH 15/17] Address second round of review comments - Fix decrypt lastLoopFlag to use payload size excluding salt+IV, preventing spurious EOF error when payload < MAX_LEN - Handle WAIT_FAILED/WAIT_ABANDONED in Windows checkStdin() - Catch TimeoutExpired in server-test.py cleanup, fall back to kill() - Also exit 77 when all individual tests are skipped (not just class) - Increase MAX_CLIENT_ARGS from 17 to 32 for worst-case arg expansion - Remove trailing whitespace on #undef lines in clu_header_main.h --- src/client/client.c | 4 ++-- src/client/clu_client_setup.c | 2 +- src/crypto/clu_decrypt.c | 11 +++++------ tests/server/server-test.py | 6 +++++- tests/wolfclu_test.py | 2 +- wolfclu/clu_header_main.h | 18 +++++++++--------- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/client/client.c b/src/client/client.c index 892edd72..6ffec9be 100644 --- a/src/client/client.c +++ b/src/client/client.c @@ -2101,8 +2101,8 @@ int checkStdin(void) } else { DWORD waitResult = WaitForSingleObject(stdinHandle, 0); - if (waitResult == WAIT_OBJECT_0) { - stop = 1; /* stdin has data or is closed */ + if (waitResult != WAIT_TIMEOUT) { + stop = 1; } } #else diff --git a/src/client/clu_client_setup.c b/src/client/clu_client_setup.c index 1f2cfd8b..cbf653ec 100644 --- a/src/client/clu_client_setup.c +++ b/src/client/clu_client_setup.c @@ -72,7 +72,7 @@ static const char sniFlag[] = "-S"; int myoptind = 0; char* myoptarg = NULL; -#define MAX_CLIENT_ARGS 17 +#define MAX_CLIENT_ARGS 32 /* return WOLFCLU_SUCCESS on success */ static int _addClientArg(const char** args, const char* in, int* idx) diff --git a/src/crypto/clu_decrypt.c b/src/crypto/clu_decrypt.c index 5eb2fc1c..a49243ec 100644 --- a/src/crypto/clu_decrypt.c +++ b/src/crypto/clu_decrypt.c @@ -77,14 +77,13 @@ int wolfCLU_decrypt(int alg, char* mode, byte* pwdKey, byte* key, int size, length = (int)XFTELL(inFile); XFSEEK(inFile, 0, SEEK_SET); - /* if there is a remainder, - * round up else no round - */ - if (length % MAX_LEN > 0) { - lastLoopFlag = (length/MAX_LEN) + 1; + /* Compute loop count from the encrypted payload size (excluding the + * salt and IV that are read separately before the loop). */ + if ((length - saltAndIvSize) % MAX_LEN > 0) { + lastLoopFlag = ((length - saltAndIvSize) / MAX_LEN) + 1; } else { - lastLoopFlag = length/MAX_LEN; + lastLoopFlag = (length - saltAndIvSize) / MAX_LEN; } input = (byte*) XMALLOC(MAX_LEN, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/tests/server/server-test.py b/tests/server/server-test.py index 4f22f674..54eae670 100644 --- a/tests/server/server-test.py +++ b/tests/server/server-test.py @@ -63,7 +63,11 @@ def test_server_client(self): f"s_client failed: {client.stderr}") finally: server.terminate() - server.wait(timeout=5) + try: + server.wait(timeout=5) + except subprocess.TimeoutExpired: + server.kill() + server.wait() if __name__ == "__main__": diff --git a/tests/wolfclu_test.py b/tests/wolfclu_test.py index fc9e6a44..35bf1634 100644 --- a/tests/wolfclu_test.py +++ b/tests/wolfclu_test.py @@ -67,6 +67,6 @@ def test_main(): result = prog.result if not result.wasSuccessful(): sys.exit(1) - if result.testsRun == 0: + if result.testsRun == 0 or len(result.skipped) == result.testsRun: sys.exit(77) sys.exit(0) diff --git a/wolfclu/clu_header_main.h b/wolfclu/clu_header_main.h index 5fe14925..f80ac610 100644 --- a/wolfclu/clu_header_main.h +++ b/wolfclu/clu_header_main.h @@ -43,16 +43,16 @@ extern "C" { #endif #endif -#undef CRL_REASON_UNSPECIFIED -#undef CRL_REASON_KEY_COMPROMISE -#undef CRL_REASON_CA_COMPROMISE -#undef CRL_REASON_AFFILIATION_CHANGED -#undef CRL_REASON_SUPERSEDED +#undef CRL_REASON_UNSPECIFIED +#undef CRL_REASON_KEY_COMPROMISE +#undef CRL_REASON_CA_COMPROMISE +#undef CRL_REASON_AFFILIATION_CHANGED +#undef CRL_REASON_SUPERSEDED #undef CRL_REASON_CESSATION_OF_OPERATION -#undef CRL_REASON_CERTIFICATE_HOLD -#undef CRL_REASON_REMOVE_FROM_CRL -#undef CRL_REASON_PRIVILEGE_WITHDRAWN -#undef CRL_REASON_AA_COMPROMISE +#undef CRL_REASON_CERTIFICATE_HOLD +#undef CRL_REASON_REMOVE_FROM_CRL +#undef CRL_REASON_PRIVILEGE_WITHDRAWN +#undef CRL_REASON_AA_COMPROMISE /* wolfssl includes */ From 6ab8cd260634b85049e66daef52ba1938f9dc22c Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 2 Apr 2026 20:13:50 +0200 Subject: [PATCH 16/17] Defer SNI insertion and validate decrypt input size - Move SNI arg insertion to after option parsing so -noservername works regardless of argument order relative to -connect - Validate input file is large enough for salt+IV before computing lastLoopFlag to prevent negative values on truncated files --- src/client/clu_client_setup.c | 19 +++++++++++-------- src/crypto/clu_decrypt.c | 7 +++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/client/clu_client_setup.c b/src/client/clu_client_setup.c index cbf653ec..16016090 100644 --- a/src/client/clu_client_setup.c +++ b/src/client/clu_client_setup.c @@ -198,14 +198,6 @@ int wolfCLU_Client(int argc, char** argv) } } - /* Set SNI hostname so modern servers accept the connection. - * Matches openssl s_client default; use -noservername to disable. */ - if (ret == WOLFCLU_SUCCESS && host != NULL && !noservername) { - ret = _addClientArg(clientArgv, sniFlag, &clientArgc); - if (ret == WOLFCLU_SUCCESS) { - ret = _addClientArg(clientArgv, host, &clientArgc); - } - } break; case WOLFCLU_STARTTLS: @@ -256,6 +248,17 @@ int wolfCLU_Client(int argc, char** argv) } } + /* Set SNI hostname so modern servers accept the connection. + * Matches openssl s_client default; use -noservername to disable. + * Deferred until after option parsing so -noservername works + * regardless of argument order. */ + if (ret == WOLFCLU_SUCCESS && host != NULL && !noservername) { + ret = _addClientArg(clientArgv, sniFlag, &clientArgc); + if (ret == WOLFCLU_SUCCESS) { + ret = _addClientArg(clientArgv, host, &clientArgc); + } + } + if (ret == WOLFCLU_SUCCESS && !verify) { ret = _addClientArg(clientArgv, noVerifyFlag, &clientArgc); diff --git a/src/crypto/clu_decrypt.c b/src/crypto/clu_decrypt.c index a49243ec..89ca968e 100644 --- a/src/crypto/clu_decrypt.c +++ b/src/crypto/clu_decrypt.c @@ -77,6 +77,13 @@ int wolfCLU_decrypt(int alg, char* mode, byte* pwdKey, byte* key, int size, length = (int)XFTELL(inFile); XFSEEK(inFile, 0, SEEK_SET); + /* Validate file is large enough for salt + IV header */ + if (length < saltAndIvSize) { + wolfCLU_LogError("Input file too small (missing salt/IV)."); + XFCLOSE(inFile); + return DECRYPT_ERROR; + } + /* Compute loop count from the encrypted payload size (excluding the * salt and IV that are read separately before the loop). */ if ((length - saltAndIvSize) % MAX_LEN > 0) { From a2437e4a8531c728b97a0ec063c84581097a6c21 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 2 Apr 2026 20:23:50 +0200 Subject: [PATCH 17/17] Fix client test to check for certificate instead of exit code s_client may return non-zero when peer verification fails (no CA bundle loaded), but the connection still succeeds and the server certificate is printed to stdout. Assert on the certificate being present rather than requiring exit code 0. --- tests/client/client-test.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/client/client-test.py b/tests/client/client-test.py index d4cf89d8..da31b389 100644 --- a/tests/client/client-test.py +++ b/tests/client/client-test.py @@ -29,19 +29,18 @@ def test_s_client_x509(self): self.addCleanup(lambda: os.remove(tmp_crt) if os.path.exists(tmp_crt) else None) - # Run s_client with empty stdin so it connects then disconnects - try: - s_client = subprocess.run( - [WOLFSSL_BIN, "s_client", "-connect", "www.google.com:443"], - input=b"\n", - capture_output=True, - timeout=30, - ) - except (subprocess.TimeoutExpired, OSError): - self.skipTest("s_client connection timed out or failed") - - if s_client.returncode != 0 or not s_client.stdout: - self.skipTest("s_client could not connect (no network?)") + # Run s_client with empty stdin so it connects then disconnects. + # Verification may fail without a CA bundle, but the connection + # still succeeds and the server certificate is printed to stdout. + s_client = subprocess.run( + [WOLFSSL_BIN, "s_client", "-connect", "www.google.com:443"], + input=b"\n", + capture_output=True, + timeout=30, + ) + + self.assertIn(b"-----BEGIN CERTIFICATE-----", s_client.stdout, + f"s_client did not return a certificate: {s_client.stderr}") # Pipe s_client stdout into x509 to extract the cert as PEM x509_extract = subprocess.run(