Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions src/ocsp/clu_ocsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

#include <wolfssl/ocsp.h>

#include <limits.h> /* for INT_MAX */

enum {
WOLFCLU_OCSP_HELP = 2100,
WOLFCLU_OCSP_IGNORE_ERR,
Expand Down Expand Up @@ -932,10 +934,23 @@ int wolfCLU_OcspSetup(int argc, char** argv)
clientConfig.noNonce = 1;
break;

case WOLFCLU_OCSP_PORT:
case WOLFCLU_OCSP_PORT: {
long pv;
if (optarg == NULL) {
wolfCLU_LogError("Option -port requires an argument: "
"must be 1-65535");
return WOLFCLU_FATAL_ERROR;
}
if (wolfCLU_parseDecimalBounded(optarg, 1, 65535, &pv)
!= WOLFCLU_SUCCESS) {
wolfCLU_LogError("Invalid port \"%s\": must be 1-65535",
Comment thread
stenslae marked this conversation as resolved.
optarg);
return WOLFCLU_FATAL_ERROR;
}
isResponderMode = 1;
responderConfig.port = (word16)XATOI(optarg);
responderConfig.port = (word16)pv;
break;
}

case WOLFCLU_OCSP_IGNORE_ERR:
wolfCLU_LogError("Option -ignore_err is not yet supported");
Expand Down Expand Up @@ -989,9 +1004,23 @@ int wolfCLU_OcspSetup(int argc, char** argv)
wolfCLU_LogError("Option -nmin is not yet supported");
return WOLFCLU_FATAL_ERROR;

case WOLFCLU_OCSP_NREQUEST:
responderConfig.nrequest = XATOI(optarg);
case WOLFCLU_OCSP_NREQUEST: {
long nr;
if (optarg == NULL) {
wolfCLU_LogError("Option -nrequest requires an argument: "
"must be 0-%d", INT_MAX);
return WOLFCLU_FATAL_ERROR;
}
/* 0 means unlimited; reject negative/non-numeric/overflow */
if (wolfCLU_parseDecimalBounded(optarg, 0, INT_MAX, &nr)
!= WOLFCLU_SUCCESS) {
wolfCLU_LogError("Invalid -nrequest \"%s\": must be 0-%d",
optarg, INT_MAX);
return WOLFCLU_FATAL_ERROR;
}
responderConfig.nrequest = (int)nr;
break;
}

case WOLFCLU_OCSP_REQIN:
wolfCLU_LogError("Option -reqin is not yet supported");
Expand Down
42 changes: 42 additions & 0 deletions src/tools/clu_funcs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,48 @@ int wolfCLU_version(void)
return WOLFCLU_SUCCESS;
}

/* parse digits-only string into [minVal, maxVal] without overflow; rejects
* sign, whitespace, empty and trailing text. only non-negative digit strings
* are accepted, so the parsed value is always >= 0 and a negative minVal can
* never reject a valid input. returns WOLFCLU_SUCCESS (sets *out) or
* WOLFCLU_FATAL_ERROR. */
int wolfCLU_parseDecimalBounded(const char* str, long minVal, long maxVal,
long* out)
{
const char* p;
long val = 0;
int over = 0;

if (str == NULL || out == NULL) {
return WOLFCLU_FATAL_ERROR;
}

/* check the bound before multiplying so val never overflows; once over,
* stop accumulating but keep scanning to reject non-digits */
for (p = str; *p != '\0'; p++) {
int digit;
if (*p < '0' || *p > '9') {
return WOLFCLU_FATAL_ERROR;
}
digit = *p - '0';
if (!over) {
if (maxVal < digit || val > (maxVal - digit) / 10) {
over = 1;
}
else {
val = (val * 10) + digit;
}
}
}

if (over || p == str || val < minVal || val > maxVal) {
return WOLFCLU_FATAL_ERROR;
}

*out = val;
return WOLFCLU_SUCCESS;
}

/* return 0 for not found and index found at otherwise */
int wolfCLU_checkForArg(const char* searchTerm, int length, int argc,
char** argv)
Expand Down
129 changes: 129 additions & 0 deletions tests/ocsp/ocsp-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,135 @@ class TestOpensslClientOpensslResponder(_OCSPInteropBase):
RESPONDER_BIN = "openssl"


class TestPortValidation(unittest.TestCase):
"""Boundary tests for the -port range check in wolfCLU_OcspSetup.

Validation happens during argument parsing, so no live responder is
needed -- an out-of-range or non-numeric port is rejected immediately.
The exact upper boundary (65535) is asserted directly here; a valid port
with no -CA fails the required-option check after parsing, so the bind
loop is never reached.
"""

@classmethod
def setUpClass(cls):
if not _ocsp_supported(WOLFSSL_BIN):
raise unittest.SkipTest(f"OCSP not supported by {WOLFSSL_BIN}")

def _run_port(self, port):
r = subprocess.run(
[WOLFSSL_BIN, "ocsp", "-port", str(port)],
capture_output=True, text=True,
stdin=subprocess.DEVNULL, timeout=10)
return r.returncode, r.stdout + r.stderr

def _assert_rejected(self, port):
rc, out = self._run_port(port)
self.assertNotEqual(rc, 0, f"port {port!r} should be rejected: {out}")
self.assertRegex(out, re.compile("invalid port|1-65535",
re.IGNORECASE),
f"expected range diagnostic for {port!r}: {out}")

def test_port_zero_rejected(self):
self._assert_rejected(0)

def test_port_negative_rejected(self):
self._assert_rejected(-1)

def test_port_above_max_rejected(self):
self._assert_rejected(65536)

def test_port_truncation_value_rejected(self):
# 65537 would have truncated to 1 under the old word16 cast.
self._assert_rejected(65537)

def test_port_non_numeric_rejected(self):
self._assert_rejected("abc")

def test_port_empty_rejected(self):
self._assert_rejected("")

def test_port_trailing_text_rejected(self):
self._assert_rejected("80x")

def test_port_int_overflow_rejected(self):
# would wrap to a valid port under the old XATOI cast
self._assert_rejected(4294967297) # 2**32 + 1

def test_port_huge_input_rejected(self):
# wider than any integer type; parser must not overflow while scanning
self._assert_rejected("9" * 40)

def test_port_max_accepted(self):
# exact upper boundary must pass the parser's overflow check; a regression
# rejecting 65535 (e.g. > vs >= slip in the bound math) is caught here.
# -CA is absent, so validation fails after parsing, never binding.
rc, out = self._run_port(65535)
self.assertNotRegex(out, re.compile("invalid port|1-65535",
re.IGNORECASE),
f"port 65535 should be accepted by parser: {out}")

def test_port_missing_argument_rejected(self):
# trailing -port leaves optarg NULL; must emit a clean missing-argument
# diagnostic, not crash formatting NULL with %s.
r = subprocess.run(
[WOLFSSL_BIN, "ocsp", "-port"],
capture_output=True, text=True,
stdin=subprocess.DEVNULL, timeout=10)
self.assertNotEqual(r.returncode, 0,
"trailing -port should be rejected")
self.assertRegex(r.stdout + r.stderr,
re.compile("requires an argument|1-65535",
re.IGNORECASE),
"expected missing-argument diagnostic for -port")


class TestNrequestValidation(unittest.TestCase):
"""Boundary tests for the -nrequest range check in wolfCLU_OcspSetup.

Validation happens during parsing. -nrequest alone never enters responder
mode (that needs -port), so a valid count exits without blocking, letting
us assert the accept path too.
"""

@classmethod
def setUpClass(cls):
if not _ocsp_supported(WOLFSSL_BIN):
raise unittest.SkipTest(f"OCSP not supported by {WOLFSSL_BIN}")

def _run(self, nrequest):
r = subprocess.run(
[WOLFSSL_BIN, "ocsp", "-nrequest", str(nrequest)],
capture_output=True, text=True,
stdin=subprocess.DEVNULL, timeout=10)
return r.stdout + r.stderr

def _assert_rejected(self, nrequest):
out = self._run(nrequest)
self.assertRegex(out, re.compile("invalid -nrequest", re.IGNORECASE),
f"expected diagnostic for {nrequest!r}: {out}")

def test_nrequest_negative_rejected(self):
# old XATOI accepted -1 as a degenerate count
self._assert_rejected(-1)

def test_nrequest_non_numeric_rejected(self):
# old XATOI returned 0, silently treated as unlimited
self._assert_rejected("abc")

def test_nrequest_overflow_rejected(self):
self._assert_rejected(4294967297) # 2**32 + 1

def test_nrequest_valid_accepted(self):
# 0-means-unlimited boundary, a small count, and the exact upper bound
# (INT_MAX) must all pass parsing; INT_MAX locks in the overflow check.
for nrequest in (0, 5, 2147483647):
out = self._run(nrequest)
self.assertNotRegex(
out, re.compile("invalid -nrequest", re.IGNORECASE),
f"-nrequest {nrequest!r} should be accepted by parser: {out}")


def load_tests(loader, tests, pattern):
"""Exclude the abstract _OCSPInteropBase from test discovery."""
suite = unittest.TestSuite()
Expand Down
10 changes: 10 additions & 0 deletions wolfclu/clu_header_main.h
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,16 @@ int wolfCLU_GetOpt(int argc, char** argv, const char *options, const struct opti
*/
int wolfCLU_getline(char **line, size_t *len, FILE *fp);

/**
* @brief Parse a digits-only string into [minVal, maxVal] without overflow.
* Rejects sign, whitespace, empty and trailing text. Because only
* non-negative digit strings are accepted, the parsed value is always
* >= 0, so a negative minVal can never reject a valid input.
* @return WOLFCLU_SUCCESS (sets *out), or WOLFCLU_FATAL_ERROR
*/
int wolfCLU_parseDecimalBounded(const char* str, long minVal, long maxVal,
long* out);

/*
* generic function to check for a specific input argument. Return the
* argv[i] where argument was found. Useful for getting following value after
Expand Down
Loading