From 556dc5ae8548b8168f7f7a87134245eaa53b29b2 Mon Sep 17 00:00:00 2001 From: Aidan Keefe Date: Mon, 31 Aug 2026 16:44:31 -0600 Subject: [PATCH 1/3] added read/write io functions that can be used to interact with any file pointer --- src/include.am | 1 + src/tools/clu_base64.c | 134 ++++++++-------------- src/tools/clu_io.c | 229 ++++++++++++++++++++++++++++++++++++++ wolfCLU.vcxproj | 1 + wolfclu/clu_header_main.h | 21 ++++ 5 files changed, 298 insertions(+), 88 deletions(-) create mode 100644 src/tools/clu_io.c diff --git a/src/include.am b/src/include.am index 776b7c65..8d1b44a2 100644 --- a/src/include.am +++ b/src/include.am @@ -9,6 +9,7 @@ wolfssl_SOURCES = src/clu_main.c \ src/tools/clu_funcs.c \ src/tools/clu_hex_to_bin.c \ src/tools/clu_rand.c \ + src/tools/clu_io.c \ src/crypto/clu_crypto_setup.c \ src/crypto/clu_encrypt.c \ src/crypto/clu_decrypt.c \ diff --git a/src/tools/clu_base64.c b/src/tools/clu_base64.c index 20a9d8b3..31f911f0 100644 --- a/src/tools/clu_base64.c +++ b/src/tools/clu_base64.c @@ -49,14 +49,15 @@ static void wolfCLU_Base64Help(void) int wolfCLU_Base64Setup(int argc, char** argv) { #if !defined(WOLFCLU_NO_FILESYSTEM) && !defined(NO_CODING) - WOLFSSL_BIO *bioIn = NULL; - WOLFSSL_BIO *bioOut = NULL; + char *bioInFile = NULL; + char *bioOutFile = NULL; byte* input = NULL; byte* output = NULL; int ret = WOLFCLU_SUCCESS; int decode = 0; int isPEM = 0; - sword32 inputSz = 8000; + /* initial buffer size to read stdin */ + word32 inputSz = 0; word32 outputSz = 0; int option; int longIndex = 1; @@ -75,20 +76,19 @@ int wolfCLU_Base64Setup(int argc, char** argv) break; case WOLFCLU_INFILE: - bioIn = wolfSSL_BIO_new_file(optarg, "rb"); - if (bioIn == NULL) { - wolfCLU_LogError("unable to open file %s", optarg); + if (optarg == NULL) { + wolfCLU_LogError("-in expected a value"); ret = WOLFCLU_FATAL_ERROR; } + bioInFile = optarg; break; case WOLFCLU_OUTFILE: - bioOut = wolfSSL_BIO_new_file(optarg, "wb"); - if (bioOut == NULL) { - wolfCLU_LogError("unable to open output file %s", - optarg); + if (optarg == NULL) { + wolfCLU_LogError("-out expected a value"); ret = WOLFCLU_FATAL_ERROR; } + bioOutFile = optarg; break; case 'd': @@ -97,12 +97,6 @@ int wolfCLU_Base64Setup(int argc, char** argv) case WOLFCLU_HELP: wolfCLU_Base64Help(); - if (bioIn != NULL) { - wolfSSL_BIO_free(bioIn); - } - if (bioOut != NULL) { - wolfSSL_BIO_free(bioOut); - } return WOLFCLU_SUCCESS; case ':': @@ -118,56 +112,39 @@ int wolfCLU_Base64Setup(int argc, char** argv) } } - if (ret == WOLFCLU_SUCCESS && bioIn == NULL) { - bioIn = wolfSSL_BIO_new(wolfSSL_BIO_s_file()); - if (bioIn != NULL) - wolfSSL_BIO_set_fp(bioIn, stdin, BIO_NOCLOSE); - } - else if (ret == WOLFCLU_SUCCESS) { - /* get data size using raw FILE pointer and seek */ - XFILE f; - if (wolfSSL_BIO_get_fp(bioIn, &f) != WOLFSSL_SUCCESS) { - wolfCLU_LogError("Unable to get raw file pointer"); - ret = WOLFCLU_FATAL_ERROR; - } - - if (ret == WOLFCLU_SUCCESS && XFSEEK(f, 0, XSEEK_END) != 0) { - wolfCLU_LogError("Unable to seek end of file"); - ret = WOLFCLU_FATAL_ERROR; - } - - if (ret == WOLFCLU_SUCCESS) { - inputSz = (sword32)XFTELL(f); - wolfSSL_BIO_reset(bioIn); - } - } - if (ret == WOLFCLU_SUCCESS) { - input = (byte*)XMALLOC(inputSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (input == NULL) { - wolfCLU_LogError("Memory allocation error for input buffer"); - ret = MEMORY_E; + if (bioInFile == NULL) { + ret = wolfCLU_readInIo(WOLFCLU_IO_STDIN, stdin, (char**)(&input), + &inputSz); } else { - inputSz = wolfSSL_BIO_read(bioIn, input, inputSz); - - if (inputSz < 0) { - wolfCLU_LogError("Could not read input."); + XFILE fp = XFOPEN(bioInFile, "rb"); + if (fp == NULL) { + wolfCLU_LogError("Could not open file %s", bioOutFile); ret = WOLFCLU_FATAL_ERROR; } - /* For decoding, check if input is in PEM format */ - else if (decode && inputSz > 11) { - /* Check if the input starts with a PEM header */ - if (XMEMCMP(input, "-----BEGIN", 10) == 0) { - isPEM = 1; - } + else { + ret = wolfCLU_readInIo(WOLFCLU_IO_FILE, fp, + (char**)(&input), &inputSz); + XFCLOSE(fp); } + } + } + /* For decoding, check if input is in PEM format */ + if (ret == WOLFCLU_SUCCESS && decode && inputSz > 11) { + /* Check if the input starts with a PEM header */ + if (XMEMCMP(input, "-----BEGIN", 10) == 0) { + isPEM = 1; } } /* Perform encoding/decoding */ - if (ret == WOLFCLU_SUCCESS && decode) { + if (ret == WOLFCLU_SUCCESS && inputSz == 0) { + /* empty input produces empty output, matching 'openssl base64' */ + outputSz = 0; + } + else if (ret == WOLFCLU_SUCCESS && decode) { if (isPEM) { #ifdef WOLFSSL_PEM_TO_DER /* Try different PEM types */ @@ -259,47 +236,34 @@ int wolfCLU_Base64Setup(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS) { - if (Base64_Encode(input, inputSz, output, &outputSz) < 0) { - wolfCLU_LogError("Base64 encode failed: %d", ret); + int encRet = Base64_Encode(input, inputSz, output, &outputSz); + if (encRet < 0) { + wolfCLU_LogError("Base64 encode failed: %d", encRet); ret = WOLFCLU_FATAL_ERROR; } - else { - ret = WOLFCLU_SUCCESS; - } } } - if (ret == WOLFCLU_SUCCESS && bioOut != NULL) { - /* Write output */ - ret = wolfSSL_BIO_write(bioOut, output, outputSz); - if (ret <= 0) { - wolfCLU_LogError("Failed to write output data: %d", ret); - ret = WOLFCLU_FATAL_ERROR; + if (ret == WOLFCLU_SUCCESS) { + if (bioOutFile == NULL) { + ret = wolfCLU_writeOutIo(WOLFCLU_IO_STDOUT, stdout, (char*)output, + outputSz); } else { - ret = WOLFCLU_SUCCESS; - } - } - else if (ret == WOLFCLU_SUCCESS) { - /* Write to stdout */ - bioOut = wolfSSL_BIO_new(wolfSSL_BIO_s_file()); - if (bioOut != NULL) { - wolfSSL_BIO_set_fp(bioOut, stdout, BIO_NOCLOSE); - ret = wolfSSL_BIO_write(bioOut, output, outputSz); - if (ret <= 0) { - wolfCLU_LogError("Failed to write to stdout: %d", ret); + XFILE fp = XFOPEN(bioOutFile, "wb"); + if (fp == NULL) { + wolfCLU_LogError("Could not open file %s", bioInFile); ret = WOLFCLU_FATAL_ERROR; } else { - ret = WOLFCLU_SUCCESS; + ret = wolfCLU_writeOutIo(WOLFCLU_IO_FILE, fp, + (char*)output, outputSz); + XFCLOSE(fp); } } - else { - wolfCLU_LogError("Failed to create stdout BIO"); - ret = MEMORY_E; - } } + /* Clean up */ if (input != NULL) { XFREE(input, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -312,12 +276,6 @@ int wolfCLU_Base64Setup(int argc, char** argv) wc_FreeDer(&der); } #endif - if (bioIn != NULL) { - wolfSSL_BIO_free(bioIn); - } - if (bioOut != NULL) { - wolfSSL_BIO_free(bioOut); - } return ret; #else diff --git a/src/tools/clu_io.c b/src/tools/clu_io.c new file mode 100644 index 00000000..1783526b --- /dev/null +++ b/src/tools/clu_io.c @@ -0,0 +1,229 @@ +#include +#include +#include + +/* Windows opens stdout and stdin in text mode, translating 0x0A <-> 0x0D 0x0A. + * We don't want that */ +#if defined(_WIN32) + #include + #include +#endif + +int wolfCLU_readInIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, char** buf, + word32* len) +{ + WOLFSSL_BIO* bio = NULL; + int ret = WOLFCLU_SUCCESS; + + struct { + char* outBuf; + sword32 len; + sword32 cap; + } buffer = {0}; + + switch (ioType) { + case WOLFCLU_IO_STDIN: + bio = wolfSSL_BIO_new_fp(fp, BIO_NOCLOSE); + if (bio == NULL) { + wolfCLU_LogError("Could not create BIO with stdin"); + return WOLFCLU_FATAL_ERROR; + } +#ifdef _WIN32 + /* Put stdin in binary mode so raw bytes pass through + * untranslated on windows. */ + (void)_setmode(_fileno(fp), _O_BINARY); +#endif + break; + + case WOLFCLU_IO_FILE: + bio = wolfSSL_BIO_new_fp(fp, BIO_NOCLOSE); + if (bio == NULL) { + wolfCLU_LogError("Could not create BIO"); + return WOLFCLU_FATAL_ERROR; + } + break; + + case WOLFCLU_IO_STDOUT: + default: + wolfCLU_LogError("Could not open file: unknown file type"); + return WOLFCLU_FATAL_ERROR; + } + + switch (ioType) { + case WOLFCLU_IO_STDIN: + { + char* tmp = NULL; + sword32 read = 0; + char cannotBump = 0; + while (1) { + if (buffer.cap == buffer.len) { + if (cannotBump == 1) { + wolfCLU_LogError("input too big needs to be %d " + "bytes or less", INT_MAX); + ret = WOLFCLU_FATAL_ERROR; + break; + } + if (buffer.cap > (INT_MAX - 1024 / 2)) { + buffer.cap = INT_MAX; + cannotBump = 1; + } + else { + buffer.cap *= 2; + buffer.cap += 1024; + } + tmp = XREALLOC(buffer.outBuf, buffer.cap, + HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + wolfCLU_LogError("Could not allocate space for io " + "read."); + ret = WOLFCLU_FATAL_ERROR; + break; + } + } + buffer.outBuf = tmp; + read = wolfSSL_BIO_read(bio, buffer.outBuf + buffer.len, + buffer.cap - buffer.len); + if (read < 0) { + wolfCLU_LogError("Error while reading from stdin."); + ret = WOLFCLU_FATAL_ERROR; + break; + } + + if (read == 0) { + break; + } + + buffer.len += read; + } + + tmp = XREALLOC(buffer.outBuf, buffer.cap, + HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + wolfCLU_LogError("Could not allocate space for io " + "read."); + ret = WOLFCLU_FATAL_ERROR; + break; + } + buffer.outBuf = tmp; + } + break; + + case WOLFCLU_IO_FILE: { + sword32 read = 0; + long fileSize = 0; + XFILE innerFp = NULL; + if (wolfSSL_BIO_get_fp(bio, &innerFp) != WOLFSSL_SUCCESS) { + wolfCLU_LogError("Could not get file pointer from BIO"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + if (XFSEEK(innerFp, 0, SEEK_END) != 0) { + wolfCLU_LogError("Could not seek input file"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + if ((fileSize = XFTELL(innerFp)) < 0) { + wolfCLU_LogError("Could not get length of file"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + if (wolfSSL_BIO_reset(bio) != WOLFSSL_SUCCESS) { + wolfCLU_LogError("Could not reset Bio"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + buffer.len = (sword32)fileSize; + if (buffer.len < 0) { + wolfCLU_LogError("Could not get length of file data"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + + buffer.outBuf = XMALLOC(buffer.len, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (buffer.outBuf == NULL) { + wolfCLU_LogError("Could not allocate space for io " + "read."); + ret = WOLFCLU_FATAL_ERROR; + break; + } + buffer.cap = buffer.len; + read = wolfSSL_BIO_read(bio, buffer.outBuf, buffer.len); + if (read != buffer.len) { + wolfCLU_LogError("Could not create BIO"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + break; + } + + case WOLFCLU_IO_STDOUT: + default: + ret = WOLFCLU_FATAL_ERROR; + } + + if (bio != NULL) { + wolfSSL_BIO_free(bio); + } + if (ret != WOLFCLU_SUCCESS) { + if (buffer.outBuf != NULL) { + XFREE(buffer.outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XMEMSET(&buffer, 0, sizeof(buffer)); + } + return ret; + } + else { + *len = buffer.len; + *buf = buffer.outBuf; + return ret; + } +} + +int wolfCLU_writeOutIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, + char* buf, word32 len) +{ + WOLFSSL_BIO* bio = NULL; + int ret = WOLFCLU_SUCCESS; + switch (ioType) { + + case WOLFCLU_IO_STDOUT: { + bio = wolfSSL_BIO_new_fp(fp, BIO_NOCLOSE); + if (bio == NULL) { + wolfCLU_LogError("Could not get BIO from stdout"); + return WOLFCLU_FATAL_ERROR; + } +#ifdef _WIN32 + /* Put stdout in binary mode so raw bytes pass through + * untranslated. */ + (void)_setmode(_fileno(stdout), _O_BINARY); +#endif + break; + } + + case WOLFCLU_IO_FILE: { + bio = wolfSSL_BIO_new_fp(fp, BIO_NOCLOSE); + if (bio == NULL) { + wolfCLU_LogError("Could not create BIO"); + return WOLFCLU_FATAL_ERROR; + } + break; + } + + case WOLFCLU_IO_STDIN: + default: + wolfCLU_LogError("Could not open BIO: Wrong Io type."); + return WOLFCLU_FATAL_ERROR; + } + + if (wolfSSL_BIO_write(bio, buf, len) != (int)len) { + wolfCLU_LogError("Could not write buffer out to target"); + ret = WOLFCLU_FATAL_ERROR; + } + + if (bio != NULL) { + wolfSSL_BIO_free(bio); + } + + return ret; +} + diff --git a/wolfCLU.vcxproj b/wolfCLU.vcxproj index 8faba25e..e0c7affc 100644 --- a/wolfCLU.vcxproj +++ b/wolfCLU.vcxproj @@ -175,6 +175,7 @@ + diff --git a/wolfclu/clu_header_main.h b/wolfclu/clu_header_main.h index b3fa9c93..24d10d83 100644 --- a/wolfclu/clu_header_main.h +++ b/wolfclu/clu_header_main.h @@ -851,6 +851,27 @@ int wolfCLU_ReadCertDer(const char* filename, byte** outDer); */ int wolfCLU_GetStdinPassword(byte* password, word32* passwordSz); +/** + * @brief Io types that passed to Io fucntions + */ +enum WOLFCLU_IO_TYPE { + WOLFCLU_IO_STDIN, + WOLFCLU_IO_STDOUT, + WOLFCLU_IO_FILE, +}; + +/* + * @brief read contence of fp to buf paramter. You must free buffer + * yourself. + */ +int wolfCLU_readInIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, char** buf, + word32* len); + +/* + * @brief write the contense of buf parameter to fp + */ +int wolfCLU_writeOutIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, + char* buf, word32 len); #ifdef __cplusplus } #endif From 4e85cad8fa4794b0841fe6779f8e99a0018dbe3b Mon Sep 17 00:00:00 2001 From: Aidan Keefe Date: Tue, 1 Sep 2026 09:44:11 -0600 Subject: [PATCH 2/3] harden io untils --- src/tools/clu_base64.c | 4 +- src/tools/clu_io.c | 239 ++++++++++++++++++++---------------- tests/base64/base64-test.py | 28 +++++ 3 files changed, 161 insertions(+), 110 deletions(-) diff --git a/src/tools/clu_base64.c b/src/tools/clu_base64.c index 31f911f0..7e60e440 100644 --- a/src/tools/clu_base64.c +++ b/src/tools/clu_base64.c @@ -120,7 +120,7 @@ int wolfCLU_Base64Setup(int argc, char** argv) else { XFILE fp = XFOPEN(bioInFile, "rb"); if (fp == NULL) { - wolfCLU_LogError("Could not open file %s", bioOutFile); + wolfCLU_LogError("Could not open file %s", bioInFile); ret = WOLFCLU_FATAL_ERROR; } else { @@ -252,7 +252,7 @@ int wolfCLU_Base64Setup(int argc, char** argv) else { XFILE fp = XFOPEN(bioOutFile, "wb"); if (fp == NULL) { - wolfCLU_LogError("Could not open file %s", bioInFile); + wolfCLU_LogError("Could not open file %s", bioOutFile); ret = WOLFCLU_FATAL_ERROR; } else { diff --git a/src/tools/clu_io.c b/src/tools/clu_io.c index 1783526b..6b424243 100644 --- a/src/tools/clu_io.c +++ b/src/tools/clu_io.c @@ -9,17 +9,134 @@ #include #endif +typedef struct WOLFCLU_IO_BUFFER { + char* outBuf; + int len; + int cap; +} WOLFCLU_IO_BUFFER; + +static int StreamRead(WOLFSSL_BIO* bio, WOLFCLU_IO_BUFFER* buffer) +{ + char* tmp = NULL; + sword32 read = 0; + char cannotBump = 0; + int ret = WOLFCLU_SUCCESS; + while (1) { + if (buffer->cap == buffer->len) { + if (cannotBump == 1) { + wolfCLU_LogError("input too big needs to be %d " + "bytes or less", INT_MAX); + ret = WOLFCLU_FATAL_ERROR; + break; + } + if (buffer->cap > ((INT_MAX - 1024) / 2)) { + buffer->cap = INT_MAX; + cannotBump = 1; + } + else { + buffer->cap *= 2; + buffer->cap += 1024; + } + tmp = XREALLOC(buffer->outBuf, buffer->cap, + HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + wolfCLU_LogError("Could not allocate space for io " + "read."); + ret = WOLFCLU_FATAL_ERROR; + break; + } + buffer->outBuf = tmp; + } + read = wolfSSL_BIO_read(bio, buffer->outBuf + buffer->len, + buffer->cap - buffer->len); + if (read < 0) { + wolfCLU_LogError("Error while reading from stdin."); + ret = WOLFCLU_FATAL_ERROR; + break; + } + + if (read == 0) { + break; + } + + buffer->len += read; + } + + /* shrink the over allocated buffer down to what was actually read. A + * zero length read is left alone since XREALLOC to 0 may free the buffer + * and hand back NULL */ + if (ret == WOLFCLU_SUCCESS && buffer->len > 0 && + buffer->len < buffer->cap) { + tmp = XREALLOC(buffer->outBuf, buffer->len, + HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + wolfCLU_LogError("Could not allocate space for io " + "read"); + ret = WOLFCLU_FATAL_ERROR; + } + else { + buffer->outBuf = tmp; + buffer->cap = buffer->len; + } + } + + return ret; +} + +static int FileRead(WOLFSSL_BIO* bio, WOLFCLU_IO_BUFFER* buffer) +{ + sword32 read = 0; + long fileSize = 0; + XFILE innerFp = NULL; + if (wolfSSL_BIO_get_fp(bio, &innerFp) != WOLFSSL_SUCCESS) { + wolfCLU_LogError("Could not get file pointer from BIO"); + return WOLFCLU_FATAL_ERROR; + } + if (XFSEEK(innerFp, 0, SEEK_END) != 0) { + wolfCLU_LogError("Could not seek input file"); + return WOLFCLU_FATAL_ERROR; + } + if ((fileSize = XFTELL(innerFp)) < 0) { + wolfCLU_LogError("Could not get length of file"); + return WOLFCLU_FATAL_ERROR; + } + if (wolfSSL_BIO_reset(bio) != WOLFSSL_SUCCESS) { + wolfCLU_LogError("Could not reset Bio"); + return WOLFCLU_FATAL_ERROR; + } + buffer->len = (sword32)fileSize; + if (buffer->len < 0) { + wolfCLU_LogError("Could not get length of file data"); + return WOLFCLU_FATAL_ERROR; + } + + buffer->outBuf = XMALLOC(buffer->len, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (buffer->outBuf == NULL) { + wolfCLU_LogError("Could not allocate space for io " + "read."); + return WOLFCLU_FATAL_ERROR; + } + buffer->cap = buffer->len; + read = wolfSSL_BIO_read(bio, buffer->outBuf, buffer->len); + if (read != buffer->len) { + wolfCLU_LogError("Could not read all of the file data"); + return WOLFCLU_FATAL_ERROR; + } + return WOLFCLU_SUCCESS; +} + int wolfCLU_readInIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, char** buf, word32* len) { WOLFSSL_BIO* bio = NULL; int ret = WOLFCLU_SUCCESS; + WOLFCLU_IO_BUFFER buffer = {0}; - struct { - char* outBuf; - sword32 len; - sword32 cap; - } buffer = {0}; + if (fp == XBADFILE || buf == NULL || len == NULL) { + wolfCLU_LogError("Bad arg passed to wolfCLU_readInIo"); + return BAD_FUNC_ARG; + } switch (ioType) { case WOLFCLU_IO_STDIN: @@ -51,111 +168,12 @@ int wolfCLU_readInIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, char** buf, switch (ioType) { case WOLFCLU_IO_STDIN: - { - char* tmp = NULL; - sword32 read = 0; - char cannotBump = 0; - while (1) { - if (buffer.cap == buffer.len) { - if (cannotBump == 1) { - wolfCLU_LogError("input too big needs to be %d " - "bytes or less", INT_MAX); - ret = WOLFCLU_FATAL_ERROR; - break; - } - if (buffer.cap > (INT_MAX - 1024 / 2)) { - buffer.cap = INT_MAX; - cannotBump = 1; - } - else { - buffer.cap *= 2; - buffer.cap += 1024; - } - tmp = XREALLOC(buffer.outBuf, buffer.cap, - HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (tmp == NULL) { - wolfCLU_LogError("Could not allocate space for io " - "read."); - ret = WOLFCLU_FATAL_ERROR; - break; - } - } - buffer.outBuf = tmp; - read = wolfSSL_BIO_read(bio, buffer.outBuf + buffer.len, - buffer.cap - buffer.len); - if (read < 0) { - wolfCLU_LogError("Error while reading from stdin."); - ret = WOLFCLU_FATAL_ERROR; - break; - } - - if (read == 0) { - break; - } - - buffer.len += read; - } - - tmp = XREALLOC(buffer.outBuf, buffer.cap, - HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (tmp == NULL) { - wolfCLU_LogError("Could not allocate space for io " - "read."); - ret = WOLFCLU_FATAL_ERROR; - break; - } - buffer.outBuf = tmp; - } + ret = StreamRead(bio, &buffer); break; - case WOLFCLU_IO_FILE: { - sword32 read = 0; - long fileSize = 0; - XFILE innerFp = NULL; - if (wolfSSL_BIO_get_fp(bio, &innerFp) != WOLFSSL_SUCCESS) { - wolfCLU_LogError("Could not get file pointer from BIO"); - ret = WOLFCLU_FATAL_ERROR; - break; - } - if (XFSEEK(innerFp, 0, SEEK_END) != 0) { - wolfCLU_LogError("Could not seek input file"); - ret = WOLFCLU_FATAL_ERROR; - break; - } - if ((fileSize = XFTELL(innerFp)) < 0) { - wolfCLU_LogError("Could not get length of file"); - ret = WOLFCLU_FATAL_ERROR; - break; - } - if (wolfSSL_BIO_reset(bio) != WOLFSSL_SUCCESS) { - wolfCLU_LogError("Could not reset Bio"); - ret = WOLFCLU_FATAL_ERROR; - break; - } - buffer.len = (sword32)fileSize; - if (buffer.len < 0) { - wolfCLU_LogError("Could not get length of file data"); - ret = WOLFCLU_FATAL_ERROR; - break; - } - - buffer.outBuf = XMALLOC(buffer.len, HEAP_HINT, - DYNAMIC_TYPE_TMP_BUFFER); - if (buffer.outBuf == NULL) { - wolfCLU_LogError("Could not allocate space for io " - "read."); - ret = WOLFCLU_FATAL_ERROR; - break; - } - buffer.cap = buffer.len; - read = wolfSSL_BIO_read(bio, buffer.outBuf, buffer.len); - if (read != buffer.len) { - wolfCLU_LogError("Could not create BIO"); - ret = WOLFCLU_FATAL_ERROR; - break; - } + case WOLFCLU_IO_FILE: + ret = FileRead(bio, &buffer); break; - } case WOLFCLU_IO_STDOUT: default: @@ -184,6 +202,11 @@ int wolfCLU_writeOutIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, { WOLFSSL_BIO* bio = NULL; int ret = WOLFCLU_SUCCESS; + if (fp == XBADFILE || (buf == NULL && len > 0) || len > INT_MAX) { + wolfCLU_LogError("Bad arg passed to wolfCLU_writeOutIo"); + return BAD_FUNC_ARG; + } + switch (ioType) { case WOLFCLU_IO_STDOUT: { @@ -215,7 +238,7 @@ int wolfCLU_writeOutIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, return WOLFCLU_FATAL_ERROR; } - if (wolfSSL_BIO_write(bio, buf, len) != (int)len) { + if (len > 0 && wolfSSL_BIO_write(bio, buf, (int)len) != (int)len) { wolfCLU_LogError("Could not write buffer out to target"); ret = WOLFCLU_FATAL_ERROR; } diff --git a/tests/base64/base64-test.py b/tests/base64/base64-test.py index c955ac71..32aac507 100644 --- a/tests/base64/base64-test.py +++ b/tests/base64/base64-test.py @@ -103,6 +103,34 @@ def test_stdin_input(self): self.assertEqual(result.returncode, 0, "Couldn't parse input from stdin") + def test_empty_stdin(self): + """Empty stdin produces empty output, matching 'openssl base64'.""" + result = subprocess.run( + [WOLFSSL_BIN, "base64"], + input=b"", + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, b"", "empty input should give no output") + + def test_empty_file(self): + """An empty input file produces an empty output file.""" + empty_file = "test-b64-empty.txt" + out_file = "test-b64-empty.b64" + self.addCleanup(lambda: os.remove(empty_file) + if os.path.exists(empty_file) else None) + self.addCleanup(lambda: os.remove(out_file) + if os.path.exists(out_file) else None) + + with open(empty_file, "wb"): + pass + + result = run_wolfssl("base64", "-in", empty_file, "-out", out_file) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(os.path.getsize(out_file), 0, + "empty input should give an empty output file") + def test_help(self): """ Test help flag """ result = subprocess.run( From bc07bd86189269b8ae9dacf303264d34e98d0b15 Mon Sep 17 00:00:00 2001 From: Aidan Keefe Date: Tue, 1 Sep 2026 14:08:36 -0600 Subject: [PATCH 3/3] added gaurd to handle valid empty input added more tests for io for base64 and rewrote IO system for base64 looking to extend in the future --- src/tools/clu_base64.c | 55 +++---- src/tools/clu_io.c | 278 +++++++++++++++++------------------- tests/base64/base64-test.py | 137 ++++++++++++++++++ wolfCLU.vcxproj | 2 +- wolfCLU.vcxproj.filters | 3 + wolfclu/clu_header_main.h | 57 ++++++-- 6 files changed, 351 insertions(+), 181 deletions(-) diff --git a/src/tools/clu_base64.c b/src/tools/clu_base64.c index 7e60e440..1cab67f1 100644 --- a/src/tools/clu_base64.c +++ b/src/tools/clu_base64.c @@ -49,14 +49,14 @@ static void wolfCLU_Base64Help(void) int wolfCLU_Base64Setup(int argc, char** argv) { #if !defined(WOLFCLU_NO_FILESYSTEM) && !defined(NO_CODING) - char *bioInFile = NULL; - char *bioOutFile = NULL; + char *inFile = NULL; + char *outFile = NULL; byte* input = NULL; byte* output = NULL; int ret = WOLFCLU_SUCCESS; int decode = 0; int isPEM = 0; - /* initial buffer size to read stdin */ + /* set by wolfCLU_ReadIo */ word32 inputSz = 0; word32 outputSz = 0; int option; @@ -80,7 +80,9 @@ int wolfCLU_Base64Setup(int argc, char** argv) wolfCLU_LogError("-in expected a value"); ret = WOLFCLU_FATAL_ERROR; } - bioInFile = optarg; + else { + inFile = optarg; + } break; case WOLFCLU_OUTFILE: @@ -88,7 +90,9 @@ int wolfCLU_Base64Setup(int argc, char** argv) wolfCLU_LogError("-out expected a value"); ret = WOLFCLU_FATAL_ERROR; } - bioOutFile = optarg; + else { + outFile = optarg; + } break; case 'd': @@ -113,27 +117,26 @@ int wolfCLU_Base64Setup(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS) { - if (bioInFile == NULL) { - ret = wolfCLU_readInIo(WOLFCLU_IO_STDIN, stdin, (char**)(&input), - &inputSz); + if (inFile == NULL) { + ret = wolfCLU_ReadIo(WOLFCLU_IO_READABLE_STREAM, stdin, &input, + &inputSz); } else { - XFILE fp = XFOPEN(bioInFile, "rb"); - if (fp == NULL) { - wolfCLU_LogError("Could not open file %s", bioInFile); + XFILE fp = XFOPEN(inFile, "rb"); + if (fp == XBADFILE) { + wolfCLU_LogError("Could not open file %s", inFile); ret = WOLFCLU_FATAL_ERROR; } else { - ret = wolfCLU_readInIo(WOLFCLU_IO_FILE, fp, - (char**)(&input), &inputSz); + ret = wolfCLU_ReadIo(WOLFCLU_IO_READABLE_FILE, fp, + &input, &inputSz); XFCLOSE(fp); } } } - /* For decoding, check if input is in PEM format */ - if (ret == WOLFCLU_SUCCESS && decode && inputSz > 11) { - /* Check if the input starts with a PEM header */ + /* when decoding, check for a PEM header on the input */ + if (ret == WOLFCLU_SUCCESS && decode && inputSz >= 10) { if (XMEMCMP(input, "-----BEGIN", 10) == 0) { isPEM = 1; } @@ -245,25 +248,27 @@ int wolfCLU_Base64Setup(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS) { - if (bioOutFile == NULL) { - ret = wolfCLU_writeOutIo(WOLFCLU_IO_STDOUT, stdout, (char*)output, + if (outFile == NULL) { + ret = wolfCLU_WriteIo(WOLFCLU_IO_WRITABLE_STREAM, stdout, output, outputSz); } else { - XFILE fp = XFOPEN(bioOutFile, "wb"); - if (fp == NULL) { - wolfCLU_LogError("Could not open file %s", bioOutFile); + XFILE fp = XFOPEN(outFile, "wb"); + if (fp == XBADFILE) { + wolfCLU_LogError("Could not open file %s", outFile); ret = WOLFCLU_FATAL_ERROR; } else { - ret = wolfCLU_writeOutIo(WOLFCLU_IO_FILE, fp, - (char*)output, outputSz); - XFCLOSE(fp); + ret = wolfCLU_WriteIo(WOLFCLU_IO_WRITABLE_FILE, fp, + output, outputSz); + if (XFCLOSE(fp) != 0 && ret == WOLFCLU_SUCCESS) { + wolfCLU_LogError("Could not write file %s", outFile); + ret = WOLFCLU_FATAL_ERROR; + } } } } - /* Clean up */ if (input != NULL) { XFREE(input, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/src/tools/clu_io.c b/src/tools/clu_io.c index 6b424243..295eed74 100644 --- a/src/tools/clu_io.c +++ b/src/tools/clu_io.c @@ -1,3 +1,24 @@ +/* clu_io.c + * + * 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 + */ + #include #include #include @@ -10,107 +31,107 @@ #endif typedef struct WOLFCLU_IO_BUFFER { - char* outBuf; + byte* outBuf; int len; int cap; } WOLFCLU_IO_BUFFER; -static int StreamRead(WOLFSSL_BIO* bio, WOLFCLU_IO_BUFFER* buffer) +/* move the data to a new allocation of newCap bytes, wiping the old one so no + * stale copies are left on the heap */ +static int ResizeBuffer(WOLFCLU_IO_BUFFER* buffer, int newCap) +{ + byte* tmp = (byte*)XMALLOC(newCap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + wolfCLU_LogError("Could not allocate space for io read."); + return WOLFCLU_FATAL_ERROR; + } + if (buffer->outBuf != NULL) { + XMEMCPY(tmp, buffer->outBuf, buffer->len); + wolfCLU_ForceZero(buffer->outBuf, buffer->len); + XFREE(buffer->outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + buffer->outBuf = tmp; + buffer->cap = newCap; + return WOLFCLU_SUCCESS; +} + +/* read a stream of unknown length, growing the buffer as we go */ +static int StreamRead(XFILE fp, WOLFCLU_IO_BUFFER* buffer) { - char* tmp = NULL; - sword32 read = 0; - char cannotBump = 0; + sword32 bytesRead = 0; + int newCap; int ret = WOLFCLU_SUCCESS; while (1) { if (buffer->cap == buffer->len) { - if (cannotBump == 1) { + if (buffer->cap == INT_MAX) { + /* try to read one more byte to make sure that if the + * one more byte is EOF we break out with a full buffer */ + char lookOneMore; + if (XFREAD(&lookOneMore, 1, 1, fp) == 0 && !XFERROR(fp)) + break; + wolfCLU_LogError("input too big needs to be %d " "bytes or less", INT_MAX); ret = WOLFCLU_FATAL_ERROR; break; } if (buffer->cap > ((INT_MAX - 1024) / 2)) { - buffer->cap = INT_MAX; - cannotBump = 1; + newCap = INT_MAX; } else { - buffer->cap *= 2; - buffer->cap += 1024; + newCap = buffer->cap * 2 + 1024; } - tmp = XREALLOC(buffer->outBuf, buffer->cap, - HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (tmp == NULL) { - wolfCLU_LogError("Could not allocate space for io " - "read."); - ret = WOLFCLU_FATAL_ERROR; + ret = ResizeBuffer(buffer, newCap); + if (ret != WOLFCLU_SUCCESS) { break; } - buffer->outBuf = tmp; } - read = wolfSSL_BIO_read(bio, buffer->outBuf + buffer->len, - buffer->cap - buffer->len); - if (read < 0) { - wolfCLU_LogError("Error while reading from stdin."); + bytesRead = (sword32)XFREAD(buffer->outBuf + buffer->len, + sizeof(*buffer->outBuf), buffer->cap - buffer->len, fp); + /* check for errors first, ports without ferror define XFERROR as 0 */ + if (bytesRead < 0 || XFERROR(fp)) { + wolfCLU_LogError("Error while reading input stream."); ret = WOLFCLU_FATAL_ERROR; break; } - - if (read == 0) { - break; + if (bytesRead == 0) { + break; /* EOF */ } - buffer->len += read; - } - - /* shrink the over allocated buffer down to what was actually read. A - * zero length read is left alone since XREALLOC to 0 may free the buffer - * and hand back NULL */ - if (ret == WOLFCLU_SUCCESS && buffer->len > 0 && - buffer->len < buffer->cap) { - tmp = XREALLOC(buffer->outBuf, buffer->len, - HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (tmp == NULL) { - wolfCLU_LogError("Could not allocate space for io " - "read"); - ret = WOLFCLU_FATAL_ERROR; - } - else { - buffer->outBuf = tmp; - buffer->cap = buffer->len; - } + buffer->len += bytesRead; } return ret; } -static int FileRead(WOLFSSL_BIO* bio, WOLFCLU_IO_BUFFER* buffer) +/* seek to get the length, then read it in one allocation. Inputs that can't + * be sized this way, like pipes and devices, are read as a stream */ +static int FileRead(XFILE fp, WOLFCLU_IO_BUFFER* buffer) { - sword32 read = 0; + sword32 bytesRead = 0; long fileSize = 0; - XFILE innerFp = NULL; - if (wolfSSL_BIO_get_fp(bio, &innerFp) != WOLFSSL_SUCCESS) { - wolfCLU_LogError("Could not get file pointer from BIO"); - return WOLFCLU_FATAL_ERROR; - } - if (XFSEEK(innerFp, 0, SEEK_END) != 0) { - wolfCLU_LogError("Could not seek input file"); - return WOLFCLU_FATAL_ERROR; + if (XFSEEK(fp, 0, XSEEK_END) != 0) { + /* not seekable, e.g. a pipe */ + return StreamRead(fp, buffer); } - if ((fileSize = XFTELL(innerFp)) < 0) { + if ((fileSize = XFTELL(fp)) < 0) { wolfCLU_LogError("Could not get length of file"); return WOLFCLU_FATAL_ERROR; } - if (wolfSSL_BIO_reset(bio) != WOLFSSL_SUCCESS) { - wolfCLU_LogError("Could not reset Bio"); + else if (fileSize > INT_MAX) { + wolfCLU_LogError("File is too large max is %d bytes", INT_MAX); return WOLFCLU_FATAL_ERROR; } - buffer->len = (sword32)fileSize; - if (buffer->len < 0) { - wolfCLU_LogError("Could not get length of file data"); + if (XFSEEK(fp, 0, XSEEK_SET) != 0) { + wolfCLU_LogError("Could not seek input file"); return WOLFCLU_FATAL_ERROR; } - - buffer->outBuf = XMALLOC(buffer->len, HEAP_HINT, + if (fileSize == 0) { + /* devices report a size of 0, an empty file just reads nothing */ + return StreamRead(fp, buffer); + } + buffer->len = (int)fileSize; + buffer->outBuf = (byte*)XMALLOC(buffer->len, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); if (buffer->outBuf == NULL) { wolfCLU_LogError("Could not allocate space for io " @@ -118,135 +139,106 @@ static int FileRead(WOLFSSL_BIO* bio, WOLFCLU_IO_BUFFER* buffer) return WOLFCLU_FATAL_ERROR; } buffer->cap = buffer->len; - read = wolfSSL_BIO_read(bio, buffer->outBuf, buffer->len); - if (read != buffer->len) { + bytesRead = (sword32)XFREAD(buffer->outBuf, sizeof(*buffer->outBuf), + buffer->len, fp); + if (bytesRead != buffer->len) { wolfCLU_LogError("Could not read all of the file data"); return WOLFCLU_FATAL_ERROR; } return WOLFCLU_SUCCESS; } -int wolfCLU_readInIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, char** buf, +int wolfCLU_ReadIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, byte** buf, word32* len) { - WOLFSSL_BIO* bio = NULL; int ret = WOLFCLU_SUCCESS; WOLFCLU_IO_BUFFER buffer = {0}; if (fp == XBADFILE || buf == NULL || len == NULL) { - wolfCLU_LogError("Bad arg passed to wolfCLU_readInIo"); + wolfCLU_LogError("Bad arg passed to wolfCLU_ReadIo"); return BAD_FUNC_ARG; } - switch (ioType) { - case WOLFCLU_IO_STDIN: - bio = wolfSSL_BIO_new_fp(fp, BIO_NOCLOSE); - if (bio == NULL) { - wolfCLU_LogError("Could not create BIO with stdin"); - return WOLFCLU_FATAL_ERROR; - } + *buf = NULL; + *len = 0; + if (!(ioType & WOLFCLU_IO_READABLE)) { + wolfCLU_LogError("Invalid ioType passed to wolfCLU_ReadIo"); + return BAD_FUNC_ARG; + } +#ifdef WOLFCLU_NO_FILESYSTEM + if (ioType & WOLFCLU_IO_RW_FILE) { + wolfCLU_LogError("Cannot read files when compiled with " + "WOLFCLU_NO_FILESYSTEM"); + return NOT_COMPILED_IN; + } +#endif #ifdef _WIN32 - /* Put stdin in binary mode so raw bytes pass through - * untranslated on windows. */ - (void)_setmode(_fileno(fp), _O_BINARY); + /* binary mode, see note at top of file */ + (void)_setmode(_fileno(fp), _O_BINARY); #endif - break; - case WOLFCLU_IO_FILE: - bio = wolfSSL_BIO_new_fp(fp, BIO_NOCLOSE); - if (bio == NULL) { - wolfCLU_LogError("Could not create BIO"); - return WOLFCLU_FATAL_ERROR; - } - break; - - case WOLFCLU_IO_STDOUT: - default: - wolfCLU_LogError("Could not open file: unknown file type"); - return WOLFCLU_FATAL_ERROR; + if (ioType & WOLFCLU_IO_READABLE_FILE) { + ret = FileRead(fp, &buffer); } - - switch (ioType) { - case WOLFCLU_IO_STDIN: - ret = StreamRead(bio, &buffer); - break; - - case WOLFCLU_IO_FILE: - ret = FileRead(bio, &buffer); - break; - - case WOLFCLU_IO_STDOUT: - default: - ret = WOLFCLU_FATAL_ERROR; + else { + ret = StreamRead(fp, &buffer); } - if (bio != NULL) { - wolfSSL_BIO_free(bio); - } if (ret != WOLFCLU_SUCCESS) { if (buffer.outBuf != NULL) { + wolfCLU_ForceZero(buffer.outBuf, buffer.len); XFREE(buffer.outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - XMEMSET(&buffer, 0, sizeof(buffer)); } return ret; } else { + if (buffer.len == 0 && buffer.outBuf != NULL) { + XFREE(buffer.outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + buffer.outBuf = NULL; + } *len = buffer.len; *buf = buffer.outBuf; return ret; } } -int wolfCLU_writeOutIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, - char* buf, word32 len) +int wolfCLU_WriteIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, + const byte* buf, word32 len) { - WOLFSSL_BIO* bio = NULL; int ret = WOLFCLU_SUCCESS; if (fp == XBADFILE || (buf == NULL && len > 0) || len > INT_MAX) { - wolfCLU_LogError("Bad arg passed to wolfCLU_writeOutIo"); + wolfCLU_LogError("Bad arg passed to wolfCLU_WriteIo"); return BAD_FUNC_ARG; } - switch (ioType) { - - case WOLFCLU_IO_STDOUT: { - bio = wolfSSL_BIO_new_fp(fp, BIO_NOCLOSE); - if (bio == NULL) { - wolfCLU_LogError("Could not get BIO from stdout"); - return WOLFCLU_FATAL_ERROR; - } + if (!(ioType & WOLFCLU_IO_WRITABLE)) { + wolfCLU_LogError("Invalid ioType passed to wolfCLU_WriteIo"); + return BAD_FUNC_ARG; + } +#ifdef WOLFCLU_NO_FILESYSTEM + if (ioType & WOLFCLU_IO_RW_FILE) { + wolfCLU_LogError("Cannot write out to a file when compiled with " + "WOLFCLU_NO_FILESYSTEM"); + return NOT_COMPILED_IN; + } +#endif #ifdef _WIN32 - /* Put stdout in binary mode so raw bytes pass through - * untranslated. */ - (void)_setmode(_fileno(stdout), _O_BINARY); + /* binary mode, see note at top of file */ + (void)_setmode(_fileno(fp), _O_BINARY); #endif - break; - } - case WOLFCLU_IO_FILE: { - bio = wolfSSL_BIO_new_fp(fp, BIO_NOCLOSE); - if (bio == NULL) { - wolfCLU_LogError("Could not create BIO"); - return WOLFCLU_FATAL_ERROR; - } - break; - } - - case WOLFCLU_IO_STDIN: - default: - wolfCLU_LogError("Could not open BIO: Wrong Io type."); - return WOLFCLU_FATAL_ERROR; - } - - if (len > 0 && wolfSSL_BIO_write(bio, buf, (int)len) != (int)len) { + if (len > 0 && XFWRITE(buf, sizeof(*buf), (int)len, fp) != len) { wolfCLU_LogError("Could not write buffer out to target"); ret = WOLFCLU_FATAL_ERROR; } - - if (bio != NULL) { - wolfSSL_BIO_free(bio); +#ifdef XFFLUSH + /* the write may only be buffered, flush so errors like a full disk are + * caught here */ + if (ret == WOLFCLU_SUCCESS && XFFLUSH(fp) != 0) { + wolfCLU_LogError("Could not flush buffer out to target"); + ret = WOLFCLU_FATAL_ERROR; } - +#endif return ret; } diff --git a/tests/base64/base64-test.py b/tests/base64/base64-test.py index 32aac507..911ad601 100644 --- a/tests/base64/base64-test.py +++ b/tests/base64/base64-test.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 """Base64 encode/decode tests for wolfCLU.""" +import base64 import filecmp import os +import random import subprocess import sys import unittest @@ -12,6 +14,13 @@ from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main +def pem_lines(data): + """Base64 encode data as 64 character lines, the way wolfssl writes it.""" + encoded = base64.b64encode(data) + return b"".join(encoded[i:i + 64] + b"\n" + for i in range(0, len(encoded), 64)) + + class Base64Test(unittest.TestCase): @classmethod @@ -103,6 +112,51 @@ def test_stdin_input(self): self.assertEqual(result.returncode, 0, "Couldn't parse input from stdin") + def test_stdin_input_long(self): + """Encode 100,000 bytes from stdin, spanning several buffer grows.""" + data = random.Random(0).randbytes(100000) + + result = subprocess.run( + [WOLFSSL_BIN, "base64"], + input=data, + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.replace(b"\n", b""), + base64.b64encode(data), + "stdin encode does not match python base64") + + def test_stdin_decode_long(self): + """Decode more than the old 8000 byte stdin limit.""" + data = random.Random(1).randbytes(100000) + + result = subprocess.run( + [WOLFSSL_BIN, "base64", "-d"], + input=pem_lines(data), + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, data, + "stdin decode does not match the original data") + + @unittest.skipUnless(os.path.exists("/dev/stdin"), "needs /dev/stdin") + def test_pipe_input_file(self): + """-in on a pipe, which can't be sized with seek, reads like stdin.""" + data = random.Random(2).randbytes(5000) + + result = subprocess.run( + [WOLFSSL_BIN, "base64", "-in", "/dev/stdin"], + input=data, + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.replace(b"\n", b""), + base64.b64encode(data), + "pipe input encode does not match python base64") + def test_empty_stdin(self): """Empty stdin produces empty output, matching 'openssl base64'.""" result = subprocess.run( @@ -114,6 +168,17 @@ def test_empty_stdin(self): self.assertEqual(result.returncode, 0, result.stderr) self.assertEqual(result.stdout, b"", "empty input should give no output") + def test_empty_stdin_decode(self): + """Empty stdin with -d produces empty output, like 'openssl base64'.""" + result = subprocess.run( + [WOLFSSL_BIN, "base64", "-d"], + input=b"", + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, b"", "empty input should give no output") + def test_empty_file(self): """An empty input file produces an empty output file.""" empty_file = "test-b64-empty.txt" @@ -131,6 +196,78 @@ def test_empty_file(self): self.assertEqual(os.path.getsize(out_file), 0, "empty input should give an empty output file") + def test_missing_input_file(self): + """A missing -in file gives a non-zero exit.""" + result = run_wolfssl("base64", "-in", "test-b64-does-not-exist.bin") + self.assertNotEqual(result.returncode, 0, + "missing input file should fail") + + def test_output_dir_missing(self): + """An -out path in a missing directory gives a non-zero exit.""" + result = run_wolfssl("base64", "-in", + os.path.join(CERTS_DIR, "server-key.der"), + "-out", os.path.join("test-b64-no-such-dir", + "out.b64")) + self.assertNotEqual(result.returncode, 0, + "output in a missing directory should fail") + + def test_failed_decode_keeps_output(self): + """A failed -d does not create or truncate the -out file.""" + out_file = "test-b64-keep.txt" + self.addCleanup(lambda: os.remove(out_file) + if os.path.exists(out_file) else None) + + with open(out_file, "wb") as f: + f.write(b"keep") + + result = run_wolfssl("base64", "-d", "-out", out_file, + stdin_data="@@@@") + self.assertNotEqual(result.returncode, 0, "bad base64 should fail") + with open(out_file, "rb") as f: + self.assertEqual(f.read(), b"keep", + "failed decode should leave -out unchanged") + + def test_in_place(self): + """-in and -out naming the same file encodes it in place.""" + work_file = "test-b64-inplace.bin" + self.addCleanup(lambda: os.remove(work_file) + if os.path.exists(work_file) else None) + + with open(os.path.join(CERTS_DIR, "server-key.der"), "rb") as f: + original = f.read() + with open(work_file, "wb") as f: + f.write(original) + + result = run_wolfssl("base64", "-in", work_file, "-out", work_file) + self.assertEqual(result.returncode, 0, result.stderr) + with open(work_file, "rb") as f: + self.assertEqual(f.read().replace(b"\n", b""), + base64.b64encode(original), + "in place encode does not match python base64") + + @unittest.skipUnless(os.path.exists("/dev/full"), "needs /dev/full") + def test_stdout_write_error(self): + """A failed write to stdout gives a non-zero exit.""" + with open("/dev/full", "wb") as full: + result = subprocess.run( + [WOLFSSL_BIN, "base64", "-in", + os.path.join(CERTS_DIR, "server-key.der")], + stdout=full, + stderr=subprocess.PIPE, + timeout=60, + ) + self.assertNotEqual(result.returncode, 0, + "write to a full device should fail") + + @unittest.skipUnless(os.path.exists("/dev/full"), "needs /dev/full") + def test_output_file_write_error(self): + """A failed write to the -out file gives a non-zero exit.""" + result = run_wolfssl("base64", "-in", + os.path.join(CERTS_DIR, "server-key.der"), + "-out", "/dev/full") + self.assertNotEqual(result.returncode, 0, + "write to a full device should fail") + def test_help(self): """ Test help flag """ result = subprocess.run( diff --git a/wolfCLU.vcxproj b/wolfCLU.vcxproj index e0c7affc..b20e3335 100644 --- a/wolfCLU.vcxproj +++ b/wolfCLU.vcxproj @@ -175,9 +175,9 @@ - + diff --git a/wolfCLU.vcxproj.filters b/wolfCLU.vcxproj.filters index 9c43d2c1..9d76d5bc 100644 --- a/wolfCLU.vcxproj.filters +++ b/wolfCLU.vcxproj.filters @@ -102,6 +102,9 @@ Source Files + + Source Files + Source Files diff --git a/wolfclu/clu_header_main.h b/wolfclu/clu_header_main.h index 24d10d83..480343bf 100644 --- a/wolfclu/clu_header_main.h +++ b/wolfclu/clu_header_main.h @@ -852,26 +852,59 @@ int wolfCLU_ReadCertDer(const char* filename, byte** outDer); int wolfCLU_GetStdinPassword(byte* password, word32* passwordSz); /** - * @brief Io types that passed to Io fucntions + * @brief IO types passed to the IO functions below */ enum WOLFCLU_IO_TYPE { - WOLFCLU_IO_STDIN, - WOLFCLU_IO_STDOUT, - WOLFCLU_IO_FILE, + /* base types */ + WOLFCLU_IO_READABLE_STREAM = 1 << 0, + WOLFCLU_IO_WRITABLE_STREAM = 1 << 1, + WOLFCLU_IO_READABLE_FILE = 1 << 2, + WOLFCLU_IO_WRITABLE_FILE = 1 << 3, + + /* type groups */ + WOLFCLU_IO_RW_STREAM = + WOLFCLU_IO_READABLE_STREAM | + WOLFCLU_IO_WRITABLE_STREAM, + + WOLFCLU_IO_RW_FILE = + WOLFCLU_IO_READABLE_FILE | + WOLFCLU_IO_WRITABLE_FILE, + + WOLFCLU_IO_READABLE = + WOLFCLU_IO_READABLE_STREAM | + WOLFCLU_IO_READABLE_FILE, + + WOLFCLU_IO_WRITABLE = + WOLFCLU_IO_WRITABLE_STREAM | + WOLFCLU_IO_WRITABLE_FILE, }; -/* - * @brief read contence of fp to buf paramter. You must free buffer - * yourself. +/** + * @brief read all of fp into a new buffer + * @param ioType a readable stream or file type. Streams are read until EOF, + * files are sized with seek first and read as a stream if that fails + * @param fp file pointer to read from + * @param buf pointer to store the buffer, NULL if empty. The caller frees it, + * wiping it first with wolfCLU_ForceZero if it holds secrets + * @param len pointer to store the number of bytes read + * @return WOLFCLU_SUCCESS on success, negative on error */ -int wolfCLU_readInIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, char** buf, +int wolfCLU_ReadIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, byte** buf, word32* len); -/* - * @brief write the contense of buf parameter to fp +/** + * @brief write len bytes of buf to fp and flush it + * @param ioType a writable stream or file type + * @param fp file pointer to write to + * @param buf buffer to write, may be NULL when len is 0 + * @param len number of bytes to write + * @return WOLFCLU_SUCCESS on success, negative on error */ -int wolfCLU_writeOutIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, - char* buf, word32 len); +int wolfCLU_WriteIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, const byte* buf, + word32 len); + + + #ifdef __cplusplus } #endif