From 6356614d0bb7f88ef90a5b3e8fc459fa4cc73c11 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Mon, 8 Jun 2026 17:24:50 -0600 Subject: [PATCH 1/4] req: adding support -addext 'subjectAltName=...' --- src/tools/clu_funcs.c | 1 + src/x509/clu_config.c | 396 +++++++++++++++++++++-------------- src/x509/clu_request_setup.c | 12 ++ tests/x509/x509-req-test.py | 26 +++ wolfclu/clu_header_main.h | 6 + wolfclu/clu_optargs.h | 1 + 6 files changed, 286 insertions(+), 156 deletions(-) diff --git a/src/tools/clu_funcs.c b/src/tools/clu_funcs.c index f409c507..e1aa427e 100644 --- a/src/tools/clu_funcs.c +++ b/src/tools/clu_funcs.c @@ -667,6 +667,7 @@ void wolfCLU_certgenHelp(void) { WOLFCLU_LOG(WOLFCLU_L0, "\t-days number of days should be valid for"); WOLFCLU_LOG(WOLFCLU_L0, "\t-x509 generate self signed certificate"); WOLFCLU_LOG(WOLFCLU_L0, "\t-extensions overwrite the section to get extensions from"); + WOLFCLU_LOG(WOLFCLU_L0, "\t-addext add an extension, ie \"subjectAltName=IP:192.168.1.2,DNS:example.com\""); WOLFCLU_LOG(WOLFCLU_L0, "\t-nodes no DES encryption on private key output"); WOLFCLU_LOG(WOLFCLU_L0, "\t-newkey generate the private key to use with req"); WOLFCLU_LOG(WOLFCLU_L0, "\t-inkey private key to use with req"); diff --git a/src/x509/clu_config.c b/src/x509/clu_config.c index f8b4c93e..07867d6b 100644 --- a/src/x509/clu_config.c +++ b/src/x509/clu_config.c @@ -331,199 +331,211 @@ static int wolfCLU_parseExtension(WOLFSSL_X509* x509, char* str, int nid, } -/* return WOLFCLU_SUCCESS on success, searches for IP's and DNS's */ -static int wolfCLU_setAltNames(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, - char* sect) +/* add a single alt name of type 'name' ("IP", "DNS", "URI", "RID" or "email") + * with value 'value' to x509, shared by the config and -addext paths. + * return WOLFCLU_SUCCESS on success */ +static int wolfCLU_addAltName(WOLFSSL_X509* x509, const char* name, + const char* value) { - WOLFSSL_STACK *altNames; - int i, ret = WOLFCLU_SUCCESS; - - if (sect == NULL) { - return WOLFCLU_SUCCESS; /* none set */ - } - + int ret = WOLFCLU_SUCCESS; #ifndef WOLFSSL_ALT_NAMES + (void)x509; + (void)name; + (void)value; WOLFCLU_LOG(WOLFCLU_L0, "Skipping alt names, recompile wolfSSL with WOLFSSL_ALT_NAMES..."); #else + WOLFSSL_ASN1_STRING *ipStr = NULL; + WOLFSSL_ASN1_OBJECT *ridObj = NULL; + char *token, *ptr, *s = NULL; + int sSz = 0; + int type = 0; + byte oid[ASN1_OID_DOTTED_MAX_SZ]; + word32 oidSz = ASN1_OID_DOTTED_MAX_SZ; + word32 decodedCount = 0; + word16 decoded[ASN1_OID_DOTTED_MAX_SZ]; + + if (XSTRNCMP(name, "IP", 2) == 0) { + ipStr = wolfSSL_a2i_IPADDRESS(value); + + if (ipStr != NULL) { + s = (char*)wolfSSL_ASN1_STRING_data(ipStr); + sSz = wolfSSL_ASN1_STRING_length(ipStr); + type = ASN_IP_TYPE; - altNames = wolfSSL_NCONF_get_section(conf, sect); - if (altNames != NULL) { - int total; + } + else { + wolfCLU_LogError("bad IP found %s", value); + return WOLFCLU_FATAL_ERROR; + } - total = wolfSSL_sk_CONF_VALUE_num(altNames); - for (i = 0; i < total; i++) { - WOLFSSL_CONF_VALUE *c; - WOLFSSL_ASN1_STRING *ipStr = NULL; - WOLFSSL_ASN1_OBJECT *ridObj = NULL; - char *token, *ptr, *s = NULL; - int sSz = 0; - int type = 0; - byte oid[ASN1_OID_DOTTED_MAX_SZ]; - word32 oidSz = ASN1_OID_DOTTED_MAX_SZ; - word32 decodedCount = 0; - word16 decoded[ASN1_OID_DOTTED_MAX_SZ]; + } - c = wolfSSL_sk_CONF_VALUE_value(altNames, i); - if (c == NULL) { - WOLFCLU_LOG(WOLFCLU_L0, "Unexpected null value found in alt " - "names stack"); - ret = WOLFCLU_FATAL_ERROR; - break; + else if (XSTRNCMP(name, "DNS", 3) == 0) { + type = ASN_DNS_TYPE; + s = (char*)value; + sSz = (int)XSTRLEN(value); + } + + else if (XSTRNCMP(name, "URI", 3) == 0) { + type = ASN_URI_TYPE; + s = (char*)value; + sSz = (int)XSTRLEN(value); + } + + else if (XSTRNCMP(name, "RID", 3) == 0) { + if ((ridObj = wolfSSL_OBJ_txt2obj(value, 0)) == NULL) { + #if defined(HAVE_OID_ENCODING) && !defined(NO_WC_ENCODE_OBJECT_ID) + /* If RID value is not named OID, manually encode + * dotted OID into byte array. Tokenize a copy so the + * original value stays intact for error messages. */ + int ridLen = (int)XSTRLEN(value); + char* ridDup = (char*)XMALLOC(ridLen + 1, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (ridDup == NULL) { + wolfCLU_LogError("Failed to allocate memory for RID"); + return MEMORY_E; } + XMEMCPY(ridDup, value, ridLen + 1); - if (XSTRNCMP(c->name, "IP", 2) == 0) { - ipStr = wolfSSL_a2i_IPADDRESS(c->value); + token = XSTRTOK(ridDup, ".", &ptr); - if (ipStr != NULL) { - s = (char*)wolfSSL_ASN1_STRING_data(ipStr); - sSz = wolfSSL_ASN1_STRING_length(ipStr); - type = ASN_IP_TYPE; + while (token != NULL) { + char* digit; + int n; + if (decodedCount >= ASN1_OID_DOTTED_MAX_SZ) { + wolfCLU_LogError("RID has too many components " + "(max %d): %s", + ASN1_OID_DOTTED_MAX_SZ, value); + ret = WOLFCLU_FATAL_ERROR; + break; + } + /* require decimal-digits-only token so that things + * like "1a" or "abc" are rejected instead of being + * silently coerced by XATOI */ + for (digit = token; *digit != '\0'; digit++) { + if (*digit < '0' || *digit > '9') { + wolfCLU_LogError("Non-numeric RID " + "component '%s' in: %s", token, + value); + ret = WOLFCLU_FATAL_ERROR; + break; + } + } + if (ret != WOLFCLU_SUCCESS) { + break; } - else { - wolfCLU_LogError("bad IP found %s", c->value); + n = XATOI(token); + if (n < 0 || n > 0xFFFF) { + wolfCLU_LogError("RID component out of range " + "[0, 65535]: %s", token); ret = WOLFCLU_FATAL_ERROR; break; } - + decoded[decodedCount] = (word16)n; + decodedCount++; + token = XSTRTOK(NULL, ".", &ptr); } - else if (XSTRNCMP(c->name, "DNS", 3) == 0) { - type = ASN_DNS_TYPE; - s = c->value; - sSz = (int)XSTRLEN(c->value); + XFREE(ridDup, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + if (ret != WOLFCLU_SUCCESS) { + return ret; } - else if (XSTRNCMP(c->name, "URI", 3) == 0) { - type = ASN_URI_TYPE; - s = c->value; - sSz = (int)XSTRLEN(c->value); + if (wc_EncodeObjectId(decoded, decodedCount, oid, &oidSz) + == 0) { + s = (char*)oid; + sSz = (int)oidSz; + } + else { + wolfCLU_LogError("bad RID found %s", value); + return WOLFCLU_FATAL_ERROR; } + #else + (void)token; + (void)ptr; + (void)decoded; + (void)decodedCount; + (void)oid; + (void)oidSz; + + wolfCLU_LogError("Couldn't encode RID. OID encoding is not" + " compiled in"); + return WOLFCLU_FATAL_ERROR; + + #endif + } + else { + s = (char*)wolfSSL_OBJ_get0_data(ridObj); + sSz = (int)wolfSSL_OBJ_length(ridObj); + } - else if (XSTRNCMP(c->name, "RID", 3) == 0) { - if ((ridObj = wolfSSL_OBJ_txt2obj(c->value, 0)) == NULL) { - #if defined(HAVE_OID_ENCODING) && !defined(NO_WC_ENCODE_OBJECT_ID) - /* If RID value is not named OID, manually encode - * dotted OID into byte array. Tokenize a copy so the - * original c->value stays intact for error messages. */ - int ridLen = (int)XSTRLEN(c->value); - char* ridDup = (char*)XMALLOC(ridLen + 1, NULL, - DYNAMIC_TYPE_TMP_BUFFER); - if (ridDup == NULL) { - wolfCLU_LogError("Failed to allocate memory for RID"); - ret = MEMORY_E; - break; - } - XMEMCPY(ridDup, c->value, ridLen + 1); - - token = XSTRTOK(ridDup, ".", &ptr); - - while (token != NULL) { - char* digit; - int n; - - if (decodedCount >= ASN1_OID_DOTTED_MAX_SZ) { - wolfCLU_LogError("RID has too many components " - "(max %d): %s", - ASN1_OID_DOTTED_MAX_SZ, c->value); - ret = WOLFCLU_FATAL_ERROR; - break; - } - /* require decimal-digits-only token so that things - * like "1a" or "abc" are rejected instead of being - * silently coerced by XATOI */ - for (digit = token; *digit != '\0'; digit++) { - if (*digit < '0' || *digit > '9') { - wolfCLU_LogError("Non-numeric RID " - "component '%s' in: %s", token, - c->value); - ret = WOLFCLU_FATAL_ERROR; - break; - } - } - if (ret != WOLFCLU_SUCCESS) { - break; - } - n = XATOI(token); - if (n < 0 || n > 0xFFFF) { - wolfCLU_LogError("RID component out of range " - "[0, 65535]: %s", token); - ret = WOLFCLU_FATAL_ERROR; - break; - } - decoded[decodedCount] = (word16)n; - decodedCount++; - token = XSTRTOK(NULL, ".", &ptr); - } - XFREE(ridDup, NULL, DYNAMIC_TYPE_TMP_BUFFER); + type = ASN_RID_TYPE; + } - if (ret != WOLFCLU_SUCCESS) { - break; - } + else if (XSTRNCMP(name, "email", 5) == 0) { + type = ASN_RFC822_TYPE; + s = (char*)value; + sSz = (int)XSTRLEN(value); + } - if (wc_EncodeObjectId(decoded, decodedCount, oid, &oidSz) - == 0) { - s = (char*)oid; - sSz = (int)oidSz; - } - else { - wolfCLU_LogError("bad RID found %s", c->value); - ret = WOLFCLU_FATAL_ERROR; - break; - } - #else - (void)token; - (void)ptr; - (void)decoded; - (void)decodedCount; - (void)oid; - (void)oidSz; - - wolfCLU_LogError("Couldn't encode RID. OID encoding is not" - " compiled in"); - ret = WOLFCLU_FATAL_ERROR; - break; + else { + wolfCLU_LogError("unsupported alt name type %s", name); + return WOLFCLU_FATAL_ERROR; + } - #endif - } - else { - s = (char*)wolfSSL_OBJ_get0_data(ridObj); - sSz = (int)wolfSSL_OBJ_length(ridObj); - } + if (wolfSSL_X509_add_altname_ex(x509, s, sSz, type) + != WOLFSSL_SUCCESS) { + wolfCLU_LogError("error adding alt name %s", value); + ret = WOLFCLU_FATAL_ERROR; + } + if (ipStr != NULL) + wolfSSL_ASN1_STRING_free(ipStr); - type = ASN_RID_TYPE; - } + if (ridObj != NULL) + wolfSSL_ASN1_OBJECT_free(ridObj); +#endif - else if (XSTRNCMP(c->name, "email", 5) == 0) { - type = ASN_RFC822_TYPE; - s = c->value; - sSz = (int)XSTRLEN(c->value); - } + return ret; +} - else { + +/* return WOLFCLU_SUCCESS on success, searches for IP's and DNS's */ +static int wolfCLU_setAltNames(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, + char* sect) +{ + WOLFSSL_STACK *altNames; + int i, ret = WOLFCLU_SUCCESS; + + if (sect == NULL) { + return WOLFCLU_SUCCESS; /* none set */ + } + + altNames = wolfSSL_NCONF_get_section(conf, sect); + if (altNames != NULL) { + int total; + + total = wolfSSL_sk_CONF_VALUE_num(altNames); + for (i = 0; i < total; i++) { + WOLFSSL_CONF_VALUE *c; + + c = wolfSSL_sk_CONF_VALUE_value(altNames, i); + if (c == NULL) { + WOLFCLU_LOG(WOLFCLU_L0, "Unexpected null value found in alt " + "names stack"); ret = WOLFCLU_FATAL_ERROR; break; } - if (wolfSSL_X509_add_altname_ex(x509, s, sSz, type) - != WOLFSSL_SUCCESS) { - wolfCLU_LogError("error adding alt name %s", c->value); - if (ipStr != NULL) - wolfSSL_ASN1_STRING_free(ipStr); - ret = WOLFCLU_FATAL_ERROR; + ret = wolfCLU_addAltName(x509, c->name, c->value); + if (ret != WOLFCLU_SUCCESS) { break; } - - if (ipStr != NULL) - wolfSSL_ASN1_STRING_free(ipStr); - - if (ridObj != NULL) - wolfSSL_ASN1_OBJECT_free(ridObj); } } -#endif return ret; } @@ -568,6 +580,69 @@ int wolfCLU_setExtensions(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, char* sect) } return ret; } + + +/* parse a command line "-addext name=value" and apply it to x509. Currently + * supports "subjectAltName=TYPE:val[,TYPE:val...]". + * return WOLFCLU_SUCCESS on success */ +int wolfCLU_parseAddExt(WOLFSSL_X509* x509, char* addExt) +{ + char* dup; + char* val; + char* token; + char* ptr = NULL; + int len; + int ret = WOLFCLU_SUCCESS; + + if (x509 == NULL || addExt == NULL) { + return BAD_FUNC_ARG; + } + + /* work on a writable copy so the original argv string is untouched */ + len = (int)XSTRLEN(addExt); + dup = (char*)XMALLOC(len + 1, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (dup == NULL) { + return MEMORY_E; + } + XMEMCPY(dup, addExt, len + 1); + + /* split "name=value" on the first '=' */ + val = XSTRSTR(dup, "="); + if (val == NULL) { + wolfCLU_LogError("-addext expects \"name=value\", got %s", addExt); + XFREE(dup, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return WOLFCLU_FATAL_ERROR; + } + *val = '\0'; + val++; + + if (XSTRCMP(dup, "subjectAltName") == 0) { + /* value is a comma separated list of TYPE:value pairs */ + token = XSTRTOK(val, ",", &ptr); + while (token != NULL) { + char* colon = XSTRSTR(token, ":"); + if (colon == NULL) { + wolfCLU_LogError("bad subjectAltName entry \"%s\", expected " + "TYPE:value", token); + ret = WOLFCLU_FATAL_ERROR; + break; + } + *colon = '\0'; + ret = wolfCLU_addAltName(x509, token, colon + 1); + if (ret != WOLFCLU_SUCCESS) { + break; + } + token = XSTRTOK(NULL, ",", &ptr); + } + } + else { + wolfCLU_LogError("unsupported -addext extension \"%s\"", dup); + ret = WOLFCLU_FATAL_ERROR; + } + + XFREE(dup, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} #else int wolfCLU_setExtensions(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, char* sect) { @@ -578,6 +653,15 @@ int wolfCLU_setExtensions(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, char* sect) wolfCLU_LogError("wolfSSL not compiled with cert extensions"); return NOT_COMPILED_IN; } + +int wolfCLU_parseAddExt(WOLFSSL_X509* x509, char* addExt) +{ + (void)x509; + (void)addExt; + + wolfCLU_LogError("wolfSSL not compiled with cert extensions"); + return NOT_COMPILED_IN; +} #endif /* WOLFSSL_CERT_EXT */ diff --git a/src/x509/clu_request_setup.c b/src/x509/clu_request_setup.c index 974edc7b..01bc3631 100644 --- a/src/x509/clu_request_setup.c +++ b/src/x509/clu_request_setup.c @@ -56,6 +56,7 @@ static const struct option req_options[] = { {"-passout", required_argument, 0, WOLFCLU_PASSWORD_OUT }, {"-noout", no_argument, 0, WOLFCLU_NOOUT }, {"-extensions",required_argument, 0, WOLFCLU_EXTENSIONS}, + {"-addext", required_argument, 0, WOLFCLU_ADDEXT }, {"-nodes", no_argument, 0, WOLFCLU_NODES }, {"-h", no_argument, 0, WOLFCLU_HELP }, {"-help", no_argument, 0, WOLFCLU_HELP }, @@ -554,6 +555,7 @@ int wolfCLU_requestSetup(int argc, char** argv) char* config = NULL; char* subj = NULL; char* ext = NULL; + char* addExt = NULL; char* keyType = NULL; char* keyInfo = NULL; char* keyOut = NULL; @@ -589,6 +591,10 @@ int wolfCLU_requestSetup(int argc, char** argv) ext = optarg; break; + case WOLFCLU_ADDEXT: + addExt = optarg; + break; + case WOLFCLU_NODES: useDes = 0; break; @@ -874,6 +880,12 @@ int wolfCLU_requestSetup(int argc, char** argv) } } + /* apply the -addext extension, if present */ + if (ret == WOLFCLU_SUCCESS && addExt != NULL) { + ret = wolfCLU_parseAddExt(x509, addExt); + reSign = 1; /* re-sign after extension change */ + } + /* if no configure is passed in then get input from command line */ if (ret == WOLFCLU_SUCCESS && subj == NULL && config == NULL && reqIn == NULL) { diff --git a/tests/x509/x509-req-test.py b/tests/x509/x509-req-test.py index 589c6035..d730f33a 100644 --- a/tests/x509/x509-req-test.py +++ b/tests/x509/x509-req-test.py @@ -211,6 +211,32 @@ def test_req_extensions_v3_alt_ca(self): self.assertIn("CA:TRUE", r2.stdout) + def test_req_x509_addext_subject_alt_name(self): + """req -x509 -addext subjectAltName adds IP and DNS alt names.""" + crt = _tmp("test_req_addext.crt") + self._clean(crt) + r = run_wolfssl("req", "-new", "-days", "3650", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", "CN=192.168.1.2", + "-addext", + "subjectAltName=IP:192.168.1.2,DNS:example.com", + "-x509", "-out", crt) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("x509", "-in", crt, "-text", "-noout") + self.assertEqual(r2.returncode, 0, r2.stderr) + found_san = False + lines = r2.stdout.splitlines() + for i, line in enumerate(lines): + if "X509v3 Subject Alternative Name" in line: + found_san = True + san_line = lines[i + 1] + self.assertIn("IP Address:192.168.1.2", san_line) + self.assertIn("DNS:example.com", san_line) + break + self.assertTrue(found_san, "SAN not found in cert output") + + class TestReqPemDerRoundTrip(unittest.TestCase): """Test PEM <-> DER round-trip for CSR.""" diff --git a/wolfclu/clu_header_main.h b/wolfclu/clu_header_main.h index a3529daa..9ba9b7b7 100644 --- a/wolfclu/clu_header_main.h +++ b/wolfclu/clu_header_main.h @@ -557,6 +557,12 @@ int wolfCLU_readConfig(WOLFSSL_X509* x509, char* config, char* sect, char* ext); */ int wolfCLU_setExtensions(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, char* sect); +/** + * @brief parse a command line "-addext name=value" argument and apply it to + * 'x509'. + */ +int wolfCLU_parseAddExt(WOLFSSL_X509* x509, char* addExt); + /** * @brief used to get an object for a given NID and set it to the given * extension diff --git a/wolfclu/clu_optargs.h b/wolfclu/clu_optargs.h index ce12e18e..e3559900 100644 --- a/wolfclu/clu_optargs.h +++ b/wolfclu/clu_optargs.h @@ -97,6 +97,7 @@ enum { WOLFCLU_SIGFILE, WOLFCLU_CONFIG, WOLFCLU_EXTENSIONS, + WOLFCLU_ADDEXT, WOLFCLU_CURVE_NAME, WOLFCLU_DAYS, WOLFCLU_SUBJECT, From 032550ffbb62405d4860610ec49c87e77706ef76 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Tue, 9 Jun 2026 15:42:30 -0600 Subject: [PATCH 2/4] Add more -addext testing --- tests/x509/x509-req-test.py | 87 +++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tests/x509/x509-req-test.py b/tests/x509/x509-req-test.py index d730f33a..61d1285e 100644 --- a/tests/x509/x509-req-test.py +++ b/tests/x509/x509-req-test.py @@ -237,6 +237,93 @@ def test_req_x509_addext_subject_alt_name(self): self.assertTrue(found_san, "SAN not found in cert output") + def _addext_san_line(self, addext, crt_name): + """Generate an -x509 cert with the given -addext and return the line + following the SAN header, or None if no SAN is present.""" + crt = _tmp(crt_name) + self._clean(crt) + r = run_wolfssl("req", "-new", "-days", "3650", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", "CN=test", + "-addext", addext, + "-x509", "-out", crt) + self.assertEqual(r.returncode, 0, r.stderr) + + r2 = run_wolfssl("x509", "-in", crt, "-text", "-noout") + self.assertEqual(r2.returncode, 0, r2.stderr) + lines = r2.stdout.splitlines() + for i, line in enumerate(lines): + if "X509v3 Subject Alternative Name" in line: + return lines[i + 1] + return None + + def test_req_addext_san_uri(self): + """req -addext subjectAltName=URI:... adds a URI alt name.""" + san = self._addext_san_line( + "subjectAltName=URI:https://www.wolfssl.com", + "test_req_addext_uri.crt") + self.assertIsNotNone(san, "SAN not found in cert output") + self.assertIn("URI:https://www.wolfssl.com", san) + + def test_req_addext_san_email(self): + """req -addext subjectAltName=email:... adds an email alt name.""" + san = self._addext_san_line( + "subjectAltName=email:facts@wolfssl.com", + "test_req_addext_email.crt") + self.assertIsNotNone(san, "SAN not found in cert output") + self.assertIn("email:facts@wolfssl.com", san) + + def test_req_addext_san_rid(self): + """req -addext subjectAltName=RID:... adds a registered ID alt name.""" + san = self._addext_san_line("subjectAltName=RID:1.2.3.4", + "test_req_addext_rid.crt") + self.assertIsNotNone(san, "SAN not found in cert output") + self.assertIn("Registered ID:1.2.3.4", san) + + def test_req_addext_san_ipv6(self): + """req -addext subjectAltName=IP: keeps colons in the value. + + The TYPE:value split consumes only the first ':' so the remaining + colons of an IPv6 literal stay part of the address.""" + san = self._addext_san_line( + "subjectAltName=IP:2607:f8b0:400a:80b::2004", + "test_req_addext_ipv6.crt") + self.assertIsNotNone(san, "SAN not found in cert output") + self.assertIn( + "IP Address:2607:F8B0:400A:080B:0000:0000:0000:2004", san) + + def _addext_fails(self, addext, crt_name): + """req -x509 with a malformed -addext must exit non-zero.""" + crt = _tmp(crt_name) + self._clean(crt) + r = run_wolfssl("req", "-new", "-days", "3650", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", "CN=test", + "-addext", addext, + "-x509", "-out", crt) + self.assertNotEqual(r.returncode, 0, + "expected failure for -addext {!r}".format(addext)) + + def test_req_addext_no_equals_fails(self): + """req -addext without '=' (name=value form) should fail.""" + self._addext_fails("subjectAltName", "test_req_addext_noeq.crt") + + def test_req_addext_san_entry_no_colon_fails(self): + """req -addext subjectAltName entry without TYPE:value should fail.""" + self._addext_fails("subjectAltName=foo", + "test_req_addext_nocolon.crt") + + def test_req_addext_unsupported_extension_fails(self): + """req -addext with an unsupported extension name should fail.""" + self._addext_fails("keyUsage=digitalSignature", + "test_req_addext_unsupported_ext.crt") + + def test_req_addext_unsupported_alt_type_fails(self): + """req -addext with an unsupported subjectAltName type should fail.""" + self._addext_fails("subjectAltName=otherName:foo", + "test_req_addext_badtype.crt") + + class TestReqPemDerRoundTrip(unittest.TestCase): """Test PEM <-> DER round-trip for CSR.""" From 5148526dd63202047fdef72ec2383565b24ac8b6 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Tue, 9 Jun 2026 15:46:15 -0600 Subject: [PATCH 3/4] Error out on repeated -addext args --- src/x509/clu_request_setup.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/x509/clu_request_setup.c b/src/x509/clu_request_setup.c index 01bc3631..eafbeaee 100644 --- a/src/x509/clu_request_setup.c +++ b/src/x509/clu_request_setup.c @@ -581,6 +581,21 @@ int wolfCLU_requestSetup(int argc, char** argv) #ifdef NO_WOLFSSL_REQ_PRINT byte isCSR = 1; #endif + /* Multiple -addext is not yet supported. Detect it up front and fail + * instead of silently dropping the extension and exiting success. */ + { + int i, addExtCount = 0; + for (i = 1; i < argc; i++) { + if (argv[i] != NULL && XSTRCMP(argv[i], "-addext") == 0) { + addExtCount++; + } + } + if (addExtCount > 1) { + wolfCLU_LogError("only one -addext arg is currently supported"); + return USER_INPUT_ERROR; + } + } + opterr = 0; /* do not display unrecognized options */ optind = 0; /* start at indent 0 */ while ((option = wolfCLU_GetOpt(argc, argv, "", req_options, From d649ba39ceb5e8499738a470632915306ea16a71 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Tue, 9 Jun 2026 15:49:17 -0600 Subject: [PATCH 4/4] Fix quirk with logging of non-WOLFSSL_ALT_NAMES per-entry --- src/x509/clu_config.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/x509/clu_config.c b/src/x509/clu_config.c index 07867d6b..49ee8c0d 100644 --- a/src/x509/clu_config.c +++ b/src/x509/clu_config.c @@ -334,16 +334,11 @@ static int wolfCLU_parseExtension(WOLFSSL_X509* x509, char* str, int nid, /* add a single alt name of type 'name' ("IP", "DNS", "URI", "RID" or "email") * with value 'value' to x509, shared by the config and -addext paths. * return WOLFCLU_SUCCESS on success */ +#ifdef WOLFSSL_ALT_NAMES static int wolfCLU_addAltName(WOLFSSL_X509* x509, const char* name, const char* value) { int ret = WOLFCLU_SUCCESS; -#ifndef WOLFSSL_ALT_NAMES - (void)x509; - (void)name; - (void)value; - WOLFCLU_LOG(WOLFCLU_L0, "Skipping alt names, recompile wolfSSL with WOLFSSL_ALT_NAMES..."); -#else WOLFSSL_ASN1_STRING *ipStr = NULL; WOLFSSL_ASN1_OBJECT *ridObj = NULL; char *token, *ptr, *s = NULL; @@ -497,10 +492,10 @@ static int wolfCLU_addAltName(WOLFSSL_X509* x509, const char* name, if (ridObj != NULL) wolfSSL_ASN1_OBJECT_free(ridObj); -#endif return ret; } +#endif /* WOLFSSL_ALT_NAMES */ /* return WOLFCLU_SUCCESS on success, searches for IP's and DNS's */ @@ -514,6 +509,12 @@ static int wolfCLU_setAltNames(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, return WOLFCLU_SUCCESS; /* none set */ } +#ifndef WOLFSSL_ALT_NAMES + (void)x509; + (void)altNames; + (void)i; + WOLFCLU_LOG(WOLFCLU_L0, "Skipping alt names, recompile wolfSSL with WOLFSSL_ALT_NAMES..."); +#else altNames = wolfSSL_NCONF_get_section(conf, sect); if (altNames != NULL) { int total; @@ -536,6 +537,7 @@ static int wolfCLU_setAltNames(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, } } } +#endif return ret; } @@ -589,8 +591,6 @@ int wolfCLU_parseAddExt(WOLFSSL_X509* x509, char* addExt) { char* dup; char* val; - char* token; - char* ptr = NULL; int len; int ret = WOLFCLU_SUCCESS; @@ -617,6 +617,13 @@ int wolfCLU_parseAddExt(WOLFSSL_X509* x509, char* addExt) val++; if (XSTRCMP(dup, "subjectAltName") == 0) { +#ifndef WOLFSSL_ALT_NAMES + (void)val; + WOLFCLU_LOG(WOLFCLU_L0, "Skipping alt names, recompile wolfSSL with WOLFSSL_ALT_NAMES..."); +#else + char* token; + char* ptr = NULL; + /* value is a comma separated list of TYPE:value pairs */ token = XSTRTOK(val, ",", &ptr); while (token != NULL) { @@ -634,6 +641,7 @@ int wolfCLU_parseAddExt(WOLFSSL_X509* x509, char* addExt) } token = XSTRTOK(NULL, ",", &ptr); } +#endif } else { wolfCLU_LogError("unsupported -addext extension \"%s\"", dup);