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/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..23ccd2f1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +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 + with: + path: wolfclu + + - 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 + 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 + run: make check + + - name: Display log + if: always() + working-directory: ./wolfclu + 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 fb1cd4cb..00000000 --- a/.github/workflows/fsanitize-check.yml +++ /dev/null @@ -1,91 +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 and nginx used for ocsp testing - sudo apt-get install -y openssl nginx - - - 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: - 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 - - 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 58a064cf..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 and nginx used for ocsp testing - sudo apt-get install -y openssl nginx - - 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 bae3bd50..05f6b035 100644 --- a/.github/workflows/windows-check.yml +++ b/.github/workflows/windows-check.yml @@ -2,63 +2,56 @@ 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@v2 - with: - repository: wolfssl/wolfssl - path: wolfssl - - - uses: actions/checkout@master - with: - path: wolfclu - - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1 + - 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: Replace user_settings.h + run: cp wolfclu/ide/winvs/user_settings.h wolfssl/IDE/WIN/user_settings.h + + - name: Detect platform toolset + id: toolset + run: | + $major = (vswhere -latest -property installationVersion).Split('.')[0] + $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 + 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 - - 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: Run tests + working-directory: wolfclu + run: python tests/run_tests.py - - 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}} diff --git a/.gitignore b/.gitignore index c8f7499c..21418665 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,8 @@ src/stamp-h1 .project .settings/ AGENTS.md +Win32/ +x64/ +.vs/ +__pycache__/ +CLAUDE.md diff --git a/Makefile.am b/Makefile.am index e63d8d9b..d27515b8 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 @@ -45,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 @@ -61,6 +65,8 @@ 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 +endif include ide/include.am #####include data/include.am diff --git a/configure.ac b/configure.ac index 1512133c..576601d0 100644 --- a/configure.ac +++ b/configure.ac @@ -55,6 +55,9 @@ AC_PROG_INSTALL AC_PROG_LN_S 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/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..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"; @@ -65,11 +67,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) @@ -99,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; @@ -193,6 +197,15 @@ int wolfCLU_Client(int argc, char** argv) &clientArgc); } } + + /* 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: @@ -225,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; @@ -264,6 +281,7 @@ int wolfCLU_Client(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS) { + StartTCP(); args.argv = (char**)clientArgv; args.argc = clientArgc; 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/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/src/x509/clu_x509_sign.c b/src/x509/clu_x509_sign.c index 7d5625cd..3c6bc255 100644 --- a/src/x509/clu_x509_sign.c +++ b/src/x509/clu_x509_sign.c @@ -1274,20 +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 - #endif + case WC_HASH_TYPE_SM3: default: wolfCLU_LogError("Unsupported hash type"); ret = WOLFCLU_FATAL_ERROR; diff --git a/tests/base64/base64-test.py b/tests/base64/base64-test.py new file mode 100644 index 00000000..671ca8a3 --- /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, test_main + + +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__": + test_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..6b27ac34 --- /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, test_main + + +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__": + test_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..d4cf89d8 --- /dev/null +++ b/tests/client/client-test.py @@ -0,0 +1,61 @@ +#!/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, test_main + + +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 + 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( + [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__": + test_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..e298be1b --- /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, test_main + +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__": + test_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..71cb6575 --- /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, test_main + + +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__": + test_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..2f4b9c8d --- /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, test_main + + +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__": + test_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..46aad58d --- /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, test_main + + +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__": + test_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..e7d2d0e4 --- /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, test_main + +# 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__": + test_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..2393efbf --- /dev/null +++ b/tests/hash/hash-test.py @@ -0,0 +1,92 @@ +#!/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, test_main + +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") + + 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) + 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__": + test_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..309371b6 --- /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, test_main + +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__": + test_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..01384071 --- /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, test_main + +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 r.returncode == 0 + 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__": + test_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..1e099b1b --- /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, test_main + +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\n", 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\n", 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\n", capture_output=True, text=False, + timeout=60, + ) + self.assertEqual(r.returncode, 0, r.stderr) + + +if __name__ == "__main__": + test_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..bc82156e --- /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, test_main + + +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__": + test_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..12e7e87b --- /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, test_main + + +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__": + test_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..037cb39f --- /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, test_main + + +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__": + test_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..d8e50ce6 --- /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, test_main + +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__": + test_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..72d966b4 --- /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, test_main + +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__": + test_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..84843fba --- /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, test_main + + +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__": + test_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..7ffb1cb7 --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,48 @@ +#!/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)) + + 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) + + +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..4f22f674 --- /dev/null +++ b/tests/server/server-test.py @@ -0,0 +1,70 @@ +#!/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, test_main + + +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.DEVNULL, stderr=subprocess.DEVNULL, + 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) + + +if __name__ == "__main__": + test_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..435c6c1b --- /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, test_main + +# 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__": + test_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..fc9e6a44 --- /dev/null +++ b/tests/wolfclu_test.py @@ -0,0 +1,72 @@ +"""Shared helpers for wolfCLU Python tests.""" + +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) + + +def _find_wolfssl_bin(): + """Locate the wolfssl binary, searching common build output paths.""" + if platform.system() == "Windows": + candidates = [ + 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(_PROJECT_ROOT, "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(_PROJECT_ROOT, "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) + + +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 new file mode 100644 index 00000000..abe01d5e --- /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, test_main + + +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__": + test_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..ca404794 --- /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, test_main + +_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__": + test_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..0ad428e1 --- /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, test_main + +TESTS_X509_DIR = os.path.join(".", "tests", "x509") +HAS_OPENSSL = shutil.which("openssl") is not None + + +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. + 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" + sig_bin = cert_path + ".sig.bin" + body_bin = cert_path + ".body.bin" + pub_pem = cert_path + ".pub.pem" + try: + subprocess.run( + ["openssl", "x509", "-inform", inform, "-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) + + with open(sig_bin, "wb") as f: + f.write(bytes.fromhex(sig_hex)) + + subprocess.run( + ["openssl", "asn1parse", "-in", stripped, "-strparse", "4", + "-out", body_bin, "-noout"], + check=True, capture_output=True, timeout=60) + + 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", inform="DER") + + 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", inform="DER") + + 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__": + test_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..9d5301bb --- /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, test_main + +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__": + test_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..4f42be6c --- /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, test_main + + +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__": + test_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 diff --git a/wolfCLU.vcxproj b/wolfCLU.vcxproj index a6017fa3..468bea77 100644 --- a/wolfCLU.vcxproj +++ b/wolfCLU.vcxproj @@ -28,22 +28,22 @@ Application true - v143 + $(DefaultPlatformToolset) Application false - v143 + $(DefaultPlatformToolset) Application true - v143 + $(DefaultPlatformToolset) Application false - v143 + $(DefaultPlatformToolset) @@ -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 9aa683f3..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 @@ -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 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 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, };