From 4665602f066d698a73e0c8db72739924842b0e96 Mon Sep 17 00:00:00 2001 From: Takashi Kojo Date: Fri, 30 Oct 2026 15:36:25 +0900 Subject: [PATCH 1/2] Add x509v3_EXT_nconf --- src/x509.c | 567 +++++++++++++++++++++++++++++++- tests/X509V3_EXT_nconf_test.cnf | 49 +++ tests/api/test_ossl_x509_ext.c | 186 +++++++++++ tests/api/test_ossl_x509_ext.h | 2 + tests/include.am | 1 + wolfssl/openssl/x509v3.h | 9 +- wolfssl/ssl.h | 5 + 7 files changed, 817 insertions(+), 2 deletions(-) create mode 100644 tests/X509V3_EXT_nconf_test.cnf diff --git a/src/x509.c b/src/x509.c index 62159e99aac..eca8acda65a 100644 --- a/src/x509.c +++ b/src/x509.c @@ -1805,7 +1805,9 @@ int wolfSSL_X509V3_EXT_print(WOLFSSL_BIO *out, WOLFSSL_X509_EXTENSION *ext, } #endif /* !NO_BIO */ -#ifndef NO_WOLFSSL_STUB +#if !defined(NO_WOLFSSL_STUB) && !(defined(OPENSSL_ALL) && \ + defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_REQ) && \ + defined(WOLFSSL_CERT_EXT)) int wolfSSL_X509V3_EXT_add_nconf(WOLFSSL_CONF *conf, WOLFSSL_X509V3_CTX *ctx, const char *section, WOLFSSL_X509 *cert) { @@ -18024,6 +18026,13 @@ void wolfSSL_X509V3_set_ctx(WOLFSSL_X509V3_CTX* ctx, WOLFSSL_X509* issuer, ret = WOLFSSL_FAILURE; WOLFSSL_MSG("wolfSSL_X509V3_set_ctx() called with null ctx."); } + else { + /* Referenced by wolfSSL_X509V3_EXT_add_nconf(). Set these first so + * they are valid even if allocating ctx->x509 below fails. */ + ctx->issuer_cert = issuer; + ctx->subject_cert = subject; + ctx->conf = NULL; + } if (ret == WOLFSSL_SUCCESS && (ctx->x509 != NULL)) { ret = WOLFSSL_FAILURE; @@ -18068,6 +18077,562 @@ void wolfSSL_X509V3_set_ctx(WOLFSSL_X509V3_CTX* ctx, WOLFSSL_X509* issuer, } } +#if defined(OPENSSL_ALL) && defined(WOLFSSL_CERT_EXT) +/* Size of a SHA-1 based key identifier. */ +#define X509V3_KEYID_SZ 20 + +/* Set the configuration used to resolve "@section" references in extension + * values passed to wolfSSL_X509V3_EXT_add_nconf(). + * + * ctx - X509v3 context + * conf - Configuration (not owned by the context) + */ +void wolfSSL_X509V3_set_nconf(WOLFSSL_X509V3_CTX* ctx, WOLFSSL_CONF* conf) +{ + WOLFSSL_ENTER("wolfSSL_X509V3_set_nconf"); + + if (ctx != NULL) { + ctx->conf = conf; + } +} + +/* Copy an extension value with the white space at either end and around the + * ',' and ':' separators removed, as OpenSSL does. White space inside a name + * or value is kept so that it is rejected or encoded as given. + * Returns the new string or NULL on failure. Caller frees with XFREE. */ +static char* x509v3_conf_dup_value(const char* value, void* heap) +{ + char* str; + size_t i = 0; + size_t j = 0; + size_t len = XSTRLEN(value); + + str = (char*)XMALLOC(len + 1, heap, DYNAMIC_TYPE_TMP_BUFFER); + if (str != NULL) { + while (i < len) { + if (XISSPACE((unsigned char)value[i])) { + size_t end = i; + + while (end < len && XISSPACE((unsigned char)value[end])) { + end++; + } + /* Keep only white space between two non-separators. */ + if (j > 0 && str[j - 1] != ',' && str[j - 1] != ':' && + end < len && value[end] != ',' && value[end] != ':') { + XMEMCPY(str + j, value + i, end - i); + j += end - i; + } + i = end; + } + else { + str[j++] = value[i++]; + } + } + str[j] = '\0'; + } + return str; +} + +/* Split off the next comma separated token. The comma is replaced with a + * terminator and *str is moved past it. Returns NULL when no tokens remain. */ +static char* x509v3_conf_next_token(char** str) +{ + char* token = *str; + char* comma; + + if (token == NULL || *token == '\0') { + return NULL; + } + comma = XSTRSTR(token, ","); + if (comma != NULL) { + *comma = '\0'; + *str = comma + 1; + } + else { + *str = token + XSTRLEN(token); + } + return token; +} + +/* Parse an OpenSSL configuration boolean. Returns 1 for true, 0 for false + * and -1 when not recognized. */ +static int x509v3_conf_bool(const char* str) +{ + if (XSTRCASECMP(str, "TRUE") == 0 || XSTRCASECMP(str, "Y") == 0 || + XSTRCASECMP(str, "YES") == 0) { + return 1; + } + if (XSTRCASECMP(str, "FALSE") == 0 || XSTRCASECMP(str, "N") == 0 || + XSTRCASECMP(str, "NO") == 0) { + return 0; + } + return -1; +} + +/* Calculate the key identifier of a certificate's public key as described in + * RFC 5280, 4.2.1.2 (1): SHA-1 of the subjectPublicKey BIT STRING value. + * This is the value OpenSSL uses for "subjectKeyIdentifier = hash". + * + * x509 - Certificate with public key set + * hash - Buffer of X509V3_KEYID_SZ bytes to hold the key identifier + * + * Returns WOLFSSL_SUCCESS or WOLFSSL_FAILURE + */ +static int x509v3_pubkey_keyid(const WOLFSSL_X509* x509, byte* hash) +{ +#ifndef NO_SHA + const byte* der; + word32 idx = 0; + word32 maxIdx; + int len; + + if (x509 == NULL || x509->pubKey.buffer == NULL || + x509->pubKey.length == 0) { + WOLFSSL_MSG("Public key not set"); + return WOLFSSL_FAILURE; + } + der = x509->pubKey.buffer; + maxIdx = x509->pubKey.length; + + /* SubjectPublicKeyInfo: SEQUENCE { AlgorithmIdentifier, BIT STRING } */ + if (GetSequence(der, &idx, &len, maxIdx) < 0) { + return WOLFSSL_FAILURE; + } + maxIdx = idx + (word32)len; + if (GetSequence(der, &idx, &len, maxIdx) < 0) { + return WOLFSSL_FAILURE; + } + idx += (word32)len; + if (CheckBitString(der, &idx, &len, maxIdx, 1, NULL) < 0) { + return WOLFSSL_FAILURE; + } + if (wc_ShaHash(der + idx, (word32)len, hash) != 0) { + return WOLFSSL_FAILURE; + } + return WOLFSSL_SUCCESS; +#else + (void)x509; + (void)hash; + WOLFSSL_MSG("SHA-1 required for key identifier"); + return WOLFSSL_FAILURE; +#endif +} + +/* basicConstraints = [critical,]CA:TRUE|FALSE[,pathlen:N] */ +static int x509v3_conf_basic_constraints(WOLFSSL_X509* cert, char* value, + int crit) +{ + char* token; + int ca = 0; + int pathLen = -1; + + while ((token = x509v3_conf_next_token(&value)) != NULL) { + if (XSTRNCASECMP(token, "CA:", 3) == 0) { + ca = x509v3_conf_bool(token + 3); + if (ca < 0) { + return WOLFSSL_FAILURE; + } + } + else if (XSTRNCASECMP(token, "pathlen:", 8) == 0) { + const char* p = token + 8; + + if (*p == '\0') { + return WOLFSSL_FAILURE; + } + for (pathLen = 0; *p != '\0'; p++) { + if (*p < '0' || *p > '9') { + return WOLFSSL_FAILURE; + } + pathLen = pathLen * 10 + (*p - '0'); + /* Encoded as a single byte INTEGER. */ + if (pathLen > 127) { + WOLFSSL_MSG("pathlen too large"); + return WOLFSSL_FAILURE; + } + } + } + else { + WOLFSSL_MSG("Unsupported basicConstraints value"); + return WOLFSSL_FAILURE; + } + } + + cert->isCa = (byte)ca; + cert->basicConstCrit = (byte)crit; + cert->basicConstSet = 1; + if (pathLen >= 0) { + cert->pathLength = (word32)pathLen; + cert->pathLengthSet = 1; + cert->basicConstPlSet = 1; + } + return WOLFSSL_SUCCESS; +} + +/* subjectKeyIdentifier = hash | none | hex string */ +static int x509v3_conf_skid(WOLFSSL_X509V3_CTX* ctx, WOLFSSL_X509* cert, + const char* value, int crit) +{ + byte skid[CTC_MAX_SKID_SIZE]; + int skidSz = 0; + + if (XSTRCASECMP(value, "none") == 0) { + return WOLFSSL_SUCCESS; + } + if (XSTRCASECMP(value, "hash") == 0) { + WOLFSSL_X509* subject = cert; + + if (ctx != NULL && ctx->subject_cert != NULL) { + subject = ctx->subject_cert; + } + if (x509v3_pubkey_keyid(subject, skid) != WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } + skidSz = X509V3_KEYID_SZ; + } + else { + /* Hex string, optionally separated with ':' as OpenSSL accepts. */ + const char* p = value; + + while (*p != '\0') { + signed char hi; + signed char lo; + + if (*p == ':') { + p++; + continue; + } + if (p[1] == '\0' || skidSz >= (int)sizeof(skid)) { + return WOLFSSL_FAILURE; + } + hi = HexCharToByte(p[0]); + lo = HexCharToByte(p[1]); + if (hi < 0 || lo < 0) { + return WOLFSSL_FAILURE; + } + skid[skidSz++] = (byte)((hi << 4) | lo); + p += 2; + } + if (skidSz == 0) { + return WOLFSSL_FAILURE; + } + } + + if (wolfSSL_X509_set_subject_key_id(cert, skid, skidSz) != + WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } + cert->subjKeyIdCrit = (byte)crit; + return WOLFSSL_SUCCESS; +} + +/* authorityKeyIdentifier = keyid[:always][,issuer[:always]] + * Only the keyIdentifier field is supported. */ +static int x509v3_conf_akid(WOLFSSL_X509V3_CTX* ctx, WOLFSSL_X509* cert, + char* value, int crit) +{ + char* token; + int keyid = 0; + int keyidAlways = 0; + int issuerAlways = 0; + WOLFSSL_X509* issuer = (ctx != NULL) ? ctx->issuer_cert : NULL; + byte hash[X509V3_KEYID_SZ]; + const byte* id = NULL; + int idSz = 0; + + while ((token = x509v3_conf_next_token(&value)) != NULL) { + if (XSTRCASECMP(token, "keyid") == 0) { + keyid = 1; + } + else if (XSTRCASECMP(token, "keyid:always") == 0) { + keyid = 1; + keyidAlways = 1; + } + else if (XSTRCASECMP(token, "issuer") == 0) { + /* Only used when the key identifier is not available. */ + } + else if (XSTRCASECMP(token, "issuer:always") == 0) { + issuerAlways = 1; + } + else { + WOLFSSL_MSG("Unsupported authorityKeyIdentifier value"); + return WOLFSSL_FAILURE; + } + } + if (issuerAlways) { + WOLFSSL_MSG("authorityKeyIdentifier issuer not supported"); + return WOLFSSL_FAILURE; + } + if (!keyid) { + return WOLFSSL_SUCCESS; + } + if (issuer == NULL) { + WOLFSSL_MSG("No issuer certificate in X509V3_CTX"); + return WOLFSSL_FAILURE; + } + + if (issuer->subjKeyIdSet && issuer->subjKeyId != NULL && + issuer->subjKeyIdSz > 0) { + id = issuer->subjKeyId; + idSz = (int)issuer->subjKeyIdSz; + } + else if (issuer == cert || (ctx != NULL && issuer == ctx->subject_cert)) { + /* Self-issued without a subject key identifier set yet: use the hash + * of the public key as OpenSSL does. */ + if (x509v3_pubkey_keyid(issuer, hash) == WOLFSSL_SUCCESS) { + id = hash; + idSz = X509V3_KEYID_SZ; + } + } + + if (id == NULL) { + if (keyidAlways) { + WOLFSSL_MSG("Unable to get issuer key identifier"); + return WOLFSSL_FAILURE; + } + return WOLFSSL_SUCCESS; + } + if (wolfSSL_X509_set_authority_key_id(cert, id, idSz) != + WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } + cert->authKeyIdCrit = (byte)crit; + return WOLFSSL_SUCCESS; +} + +/* Add one subjectAltName entry: type is DNS, IP, email or URI. */ +static int x509v3_conf_add_altname(WOLFSSL_X509* cert, const char* type, + const char* name) +{ + int asnType; + + if (XSTRCASECMP(type, "DNS") == 0) { + asnType = ASN_DNS_TYPE; + } + else if (XSTRCASECMP(type, "IP") == 0) { + asnType = ASN_IP_TYPE; + } + else if (XSTRCASECMP(type, "email") == 0) { + if (XSTRCASECMP(name, "copy") == 0 || XSTRCASECMP(name, "move") == 0) { + WOLFSSL_MSG("email:copy/move not supported"); + return WOLFSSL_FAILURE; + } + asnType = ASN_RFC822_TYPE; + } + else if (XSTRCASECMP(type, "URI") == 0) { + asnType = ASN_URI_TYPE; + } + else { + WOLFSSL_MSG("Unsupported subjectAltName type"); + return WOLFSSL_FAILURE; + } + if (*name == '\0') { + return WOLFSSL_FAILURE; + } + return wolfSSL_X509_add_altname(cert, name, asnType); +} + +/* subjectAltName = TYPE:value[,TYPE:value...] | @section + * In a section the names are TYPE or TYPE.n (e.g. DNS.1). */ +static int x509v3_conf_san_add(WOLFSSL_CONF* conf, WOLFSSL_X509* cert, + char* value) +{ + char* token; + char* sep; + + while ((token = x509v3_conf_next_token(&value)) != NULL) { + if (token[0] == '@') { + WOLFSSL_STACK* sk; + WOLFSSL_CONF_VALUE* val; + char type[16]; + int i; + int num; + + sk = wolfSSL_NCONF_get_section(conf, token + 1); + if (sk == NULL) { + WOLFSSL_MSG("subjectAltName section not found"); + return WOLFSSL_FAILURE; + } + num = wolfSSL_sk_CONF_VALUE_num(sk); + for (i = 0; i < num; i++) { + size_t typeLen; + + val = wolfSSL_sk_CONF_VALUE_value(sk, i); + if (val == NULL || val->name == NULL || val->value == NULL) { + return WOLFSSL_FAILURE; + } + sep = XSTRSTR(val->name, "."); + typeLen = (sep != NULL) ? (size_t)(sep - val->name) : + XSTRLEN(val->name); + if (typeLen >= sizeof(type)) { + return WOLFSSL_FAILURE; + } + XMEMCPY(type, val->name, typeLen); + type[typeLen] = '\0'; + if (x509v3_conf_add_altname(cert, type, val->value) != + WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } + } + } + else { + sep = XSTRSTR(token, ":"); + if (sep == NULL) { + return WOLFSSL_FAILURE; + } + *sep = '\0'; + if (x509v3_conf_add_altname(cert, token, sep + 1) != + WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } + } + } + return WOLFSSL_SUCCESS; +} + +/* Add all the subjectAltName entries of a value, or none of them on error. */ +static int x509v3_conf_san(WOLFSSL_CONF* conf, WOLFSSL_X509* cert, + char* value) +{ + DNS_entry* prev = cert->altNames; + int ret; + + ret = x509v3_conf_san_add(conf, cert, value); + if (ret != WOLFSSL_SUCCESS && cert->altNames != prev) { + /* New entries are put at the head of the list: free those. */ + DNS_entry* added = cert->altNames; + DNS_entry* last = added; + + while (last->next != prev) { + last = last->next; + } + last->next = NULL; + FreeAltNames(added, cert->heap); + cert->altNames = prev; + } + return ret; +} + +/* Apply one "name = value" extension entry to the certificate. */ +static int x509v3_conf_add_value(WOLFSSL_CONF* conf, WOLFSSL_X509V3_CTX* ctx, + WOLFSSL_X509* cert, const char* name, const char* value) +{ + int ret = WOLFSSL_FAILURE; + int nid; + int crit = 0; + char* str; + char* v; + + nid = wolfSSL_OBJ_sn2nid(name); + if (nid == WC_NID_undef) { + WOLFSSL_MSG("Unknown extension name"); + return WOLFSSL_FAILURE; + } + + str = x509v3_conf_dup_value(value, cert->heap); + if (str == NULL) { + return WOLFSSL_FAILURE; + } + v = str; + if (XSTRNCASECMP(v, "critical", 8) == 0 && + (v[8] == ',' || v[8] == '\0')) { + crit = 1; + v += (v[8] == ',') ? 9 : 8; + } + + switch (nid) { + case WC_NID_basic_constraints: + ret = x509v3_conf_basic_constraints(cert, v, crit); + break; + case WC_NID_key_usage: + /* Always encoded as critical. */ + if (ParseKeyUsageStr(v, &cert->keyUsage, cert->heap) == 0) { + cert->keyUsageCrit = (byte)crit; + cert->keyUsageSet = 1; + ret = WOLFSSL_SUCCESS; + } + break; + case WC_NID_ext_key_usage: + /* Always encoded as non-critical. */ + if (ParseExtKeyUsageStr(v, &cert->extKeyUsage, cert->heap) == 0) { + cert->extKeyUsageCrit = (byte)crit; + ret = WOLFSSL_SUCCESS; + } + break; + case WC_NID_subject_key_identifier: + ret = x509v3_conf_skid(ctx, cert, v, crit); + break; + case WC_NID_authority_key_identifier: + ret = x509v3_conf_akid(ctx, cert, v, crit); + break; + case WC_NID_subject_alt_name: + /* Criticality is not stored for subjectAltName. */ + ret = x509v3_conf_san(conf, cert, v); + break; + default: + WOLFSSL_MSG("Unsupported extension in configuration"); + break; + } + + if (ret != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("Failed to add extension from configuration"); + } + XFREE(str, cert->heap, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +/* Add the extensions in a configuration section to a certificate. + * + * Supported: basicConstraints, keyUsage, extendedKeyUsage, + * subjectKeyIdentifier, authorityKeyIdentifier (keyid) and subjectAltName + * (DNS, IP, email, URI, @section). + * + * conf - Configuration holding the section + * ctx - X509v3 context set with wolfSSL_X509V3_set_ctx(). The issuer + * certificate is used for authorityKeyIdentifier. May be NULL. + * section - Name of the section listing the extensions + * cert - Certificate to add the extensions to + * + * Returns WOLFSSL_SUCCESS or WOLFSSL_FAILURE + */ +int wolfSSL_X509V3_EXT_add_nconf(WOLFSSL_CONF *conf, WOLFSSL_X509V3_CTX *ctx, + const char *section, WOLFSSL_X509 *cert) +{ + WOLFSSL_STACK* sk; + WOLFSSL_CONF_VALUE* val; + WOLFSSL_CONF* refConf; + int i; + int num; + + WOLFSSL_ENTER("wolfSSL_X509V3_EXT_add_nconf"); + + if (conf == NULL || section == NULL || cert == NULL) { + return WOLFSSL_FAILURE; + } + + sk = wolfSSL_NCONF_get_section(conf, section); + if (sk == NULL) { + WOLFSSL_MSG("Extension section not found"); + return WOLFSSL_FAILURE; + } + /* "@section" references are resolved in the configuration set with + * wolfSSL_X509V3_set_nconf(), or in this one if none was set. */ + refConf = (ctx != NULL && ctx->conf != NULL) ? ctx->conf : conf; + + num = wolfSSL_sk_CONF_VALUE_num(sk); + for (i = 0; i < num; i++) { + val = wolfSSL_sk_CONF_VALUE_value(sk, i); + if (val == NULL || val->name == NULL || val->value == NULL) { + return WOLFSSL_FAILURE; + } + if (x509v3_conf_add_value(refConf, ctx, cert, val->name, + val->value) != WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } + } + return WOLFSSL_SUCCESS; +} +#endif /* OPENSSL_ALL && WOLFSSL_CERT_EXT */ + #ifndef NO_BIO int wolfSSL_i2d_X509_REQ(WOLFSSL_X509* req, unsigned char** out) { diff --git a/tests/X509V3_EXT_nconf_test.cnf b/tests/X509V3_EXT_nconf_test.cnf new file mode 100644 index 00000000000..4ce405553f2 --- /dev/null +++ b/tests/X509V3_EXT_nconf_test.cnf @@ -0,0 +1,49 @@ +# +# Extension sections used to test wolfSSL_X509V3_EXT_add_nconf +# + +[ v3_ca ] +basicConstraints = critical, CA:TRUE, pathlen:1 +keyUsage = critical, keyCertSign, cRLSign +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +[ v3_server ] +basicConstraints = CA:FALSE +keyUsage = critical, digitalSignature +extendedKeyUsage = serverAuth, clientAuth +subjectKeyIdentifier = 01:02:03:04 +authorityKeyIdentifier = keyid:always +subjectAltName = DNS:www.example.com, @alt_names + +[ alt_names ] +DNS.1 = a.example.com +email.1 = user@example.com +URI.1 = https://example.com/ + +[ bad_name ] +unknownExtension = value + +[ bad_key_usage ] +keyUsage = notAKeyUsage + +[ bad_akid ] +authorityKeyIdentifier = issuer:always + +[ bad_san ] +subjectAltName = otherName:value + +[ bad_empty_token ] +basicConstraints = CA:TRUE,,pathlen:1 + +[ bad_pathlen ] +basicConstraints = CA:TRUE, pathlen:128 + +[ bad_skid ] +subjectKeyIdentifier = 01 02 + +[ bad_ip ] +subjectAltName = IP:999.1.1.1 + +[ bad_san_partial ] +subjectAltName = DNS:added.example.com, URI: diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index af2bedc46e2..28b37b3f5f4 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -1213,8 +1213,15 @@ int test_wolfSSL_X509V3_EXT_nconf(void) X509_free(x509); #ifndef NO_WOLFSSL_STUB +#if defined(OPENSSL_ALL) && defined(WOLFSSL_CERT_GEN) && \ + defined(WOLFSSL_CERT_REQ) && defined(WOLFSSL_CERT_EXT) + /* Implemented: see test_wolfSSL_X509V3_EXT_add_nconf(). */ + ExpectIntEQ(wolfSSL_X509V3_EXT_add_nconf(NULL, NULL, NULL, NULL), + WOLFSSL_FAILURE); +#else ExpectIntEQ(wolfSSL_X509V3_EXT_add_nconf(NULL, NULL, NULL, NULL), WOLFSSL_SUCCESS); +#endif ExpectNull(wolfSSL_X509V3_EXT_conf_nid(NULL, NULL, 0, NULL)); ExpectNull(wolfSSL_X509V3_EXT_conf_nid(&lhash, NULL, 0, NULL)); wolfSSL_X509V3_set_ctx_nodb(NULL); @@ -1223,6 +1230,185 @@ int test_wolfSSL_X509V3_EXT_nconf(void) return EXPECT_RESULT(); } +int test_wolfSSL_X509V3_EXT_add_nconf(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_ALL) && defined(WOLFSSL_CERT_GEN) && \ + defined(WOLFSSL_CERT_REQ) && defined(WOLFSSL_CERT_EXT) && \ + !defined(NO_FILESYSTEM) && !defined(NO_BIO) && defined(HAVE_ECC) && \ + !defined(NO_SHA) && !defined(NO_SHA256) + const char* confFile = "./tests/X509V3_EXT_nconf_test.cnf"; + const byte serverSkid[] = { 0x01, 0x02, 0x03, 0x04 }; + CONF* conf = NULL; + long eline = 0; + X509V3_CTX ctx; + X509* caCert = NULL; + X509* srvCert = NULL; + X509* root = NULL; + X509* leaf = NULL; + X509* decoded = NULL; + EVP_PKEY* pub = NULL; + EVP_PKEY* caKey = NULL; + BIO* bio = NULL; + const ASN1_STRING* expSkid = NULL; + const ASN1_STRING* skid = NULL; + GENERAL_NAMES* names = NULL; + BASIC_CONSTRAINTS* bc = NULL; + unsigned char* der = NULL; + const unsigned char* p = NULL; + int derSz = 0; + DNS_entry* dns = NULL; + int sanCnt = 0; + + ExpectNotNull(conf = NCONF_new(NULL)); + ExpectIntEQ(NCONF_load(conf, confFile, &eline), 1); + ExpectNotNull(caCert = wolfSSL_X509_load_certificate_file( + "./certs/ca-ecc-cert.pem", WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(srvCert = wolfSSL_X509_load_certificate_file( + "./certs/server-ecc.pem", WOLFSSL_FILETYPE_PEM)); + /* SKID of ca-ecc-cert.pem was made by OpenSSL with + * "subjectKeyIdentifier = hash". */ + ExpectNotNull(expSkid = X509_get0_subject_key_id(caCert)); + + /* Self-signed CA built from the public key of ca-ecc-cert.pem. */ + ExpectNotNull(root = X509_new()); + ExpectNotNull(pub = X509_get_pubkey(caCert)); + ExpectIntEQ(X509_set_pubkey(root, pub), WOLFSSL_SUCCESS); + EVP_PKEY_free(pub); + pub = NULL; + + XMEMSET(&ctx, 0, sizeof(ctx)); + X509V3_set_ctx(&ctx, root, root, NULL, NULL, 0); + X509V3_set_nconf(&ctx, conf); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "v3_ca", root), + WOLFSSL_SUCCESS); + wolfSSL_X509_free(ctx.x509); + ctx.x509 = NULL; + + ExpectIntEQ(wolfSSL_X509_get_isCA(root), 1); + ExpectIntEQ(X509_get_key_usage(root), KU_KEY_CERT_SIGN | KU_CRL_SIGN); + ExpectNotNull(skid = X509_get0_subject_key_id(root)); + ExpectIntEQ(ASN1_STRING_length(skid), ASN1_STRING_length(expSkid)); + ExpectBufEQ(ASN1_STRING_get0_data(skid), ASN1_STRING_get0_data(expSkid), + ASN1_STRING_length(expSkid)); + /* Self-signed: authority key identifier is its own key identifier. */ + if (EXPECT_SUCCESS() && root != NULL) { + ExpectIntEQ(root->authKeyIdSz, ASN1_STRING_length(expSkid)); + ExpectBufEQ(root->authKeyId, ASN1_STRING_get0_data(expSkid), + ASN1_STRING_length(expSkid)); + } + + /* Leaf issued by root, signed and parsed back. */ + ExpectNotNull(leaf = X509_new()); + ExpectNotNull(pub = X509_get_pubkey(srvCert)); + ExpectIntEQ(X509_set_pubkey(leaf, pub), WOLFSSL_SUCCESS); + EVP_PKEY_free(pub); + pub = NULL; + ExpectIntEQ(X509_set_version(leaf, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(X509_set_subject_name(leaf, X509_get_subject_name(srvCert)), + WOLFSSL_SUCCESS); + ExpectIntEQ(X509_set_issuer_name(leaf, X509_get_subject_name(caCert)), + WOLFSSL_SUCCESS); + ExpectIntEQ(X509_set1_notBefore(leaf, X509_get0_notBefore(srvCert)), + WOLFSSL_SUCCESS); + ExpectIntEQ(X509_set1_notAfter(leaf, X509_get0_notAfter(srvCert)), + WOLFSSL_SUCCESS); + + XMEMSET(&ctx, 0, sizeof(ctx)); + X509V3_set_ctx(&ctx, root, leaf, NULL, NULL, 0); + X509V3_set_nconf(&ctx, conf); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "v3_server", leaf), + WOLFSSL_SUCCESS); + wolfSSL_X509_free(ctx.x509); + ctx.x509 = NULL; + + ExpectNotNull(bio = BIO_new_file("./certs/ca-ecc-key.pem", "rb")); + ExpectNotNull(caKey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL)); + BIO_free(bio); + bio = NULL; + ExpectIntGT(X509_sign(leaf, caKey, EVP_sha256()), 0); + ExpectIntGT(derSz = i2d_X509(leaf, &der), 0); + p = der; + ExpectNotNull(decoded = d2i_X509(NULL, &p, derSz)); + + ExpectNotNull(bc = (BASIC_CONSTRAINTS*)X509_get_ext_d2i(decoded, + NID_basic_constraints, NULL, NULL)); + if (bc != NULL) { + ExpectIntEQ(bc->ca, 0); + } + BASIC_CONSTRAINTS_free(bc); + ExpectIntEQ(X509_get_key_usage(decoded), KU_DIGITAL_SIGNATURE); + ExpectIntEQ(X509_get_extended_key_usage(decoded), + XKU_SSL_SERVER | XKU_SSL_CLIENT); + ExpectNotNull(skid = X509_get0_subject_key_id(decoded)); + ExpectIntEQ(ASN1_STRING_length(skid), (int)sizeof(serverSkid)); + ExpectBufEQ(ASN1_STRING_get0_data(skid), serverSkid, sizeof(serverSkid)); + if (EXPECT_SUCCESS() && decoded != NULL) { + ExpectIntEQ(decoded->authKeyIdSz, ASN1_STRING_length(expSkid)); + ExpectBufEQ(decoded->authKeyId, ASN1_STRING_get0_data(expSkid), + ASN1_STRING_length(expSkid)); + } + ExpectNotNull(names = (GENERAL_NAMES*)X509_get_ext_d2i(decoded, + NID_subject_alt_name, NULL, NULL)); + ExpectIntEQ(sk_GENERAL_NAME_num(names), 4); + GENERAL_NAMES_free(names); + + /* Error cases. */ + ExpectIntEQ(X509V3_EXT_add_nconf(NULL, &ctx, "v3_ca", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, NULL, root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "v3_ca", NULL), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "missing", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_name", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_key_usage", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_akid", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_san", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_empty_token", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_pathlen", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_skid", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_ip", root), + WOLFSSL_FAILURE); + /* A failing subjectAltName value leaves the existing names unchanged. */ + for (dns = (leaf != NULL) ? leaf->altNames : NULL; dns != NULL; + dns = dns->next) { + sanCnt++; + } + ExpectIntEQ(sanCnt, 4); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_san_partial", leaf), + WOLFSSL_FAILURE); + sanCnt = 0; + for (dns = (leaf != NULL) ? leaf->altNames : NULL; dns != NULL; + dns = dns->next) { + sanCnt++; + } + ExpectIntEQ(sanCnt, 4); + /* No issuer certificate for authorityKeyIdentifier. */ + ExpectIntEQ(X509V3_EXT_add_nconf(conf, NULL, "v3_server", leaf), + WOLFSSL_FAILURE); + X509V3_set_nconf(NULL, conf); + + XFREE(der, NULL, DYNAMIC_TYPE_OPENSSL); + X509_free(decoded); + EVP_PKEY_free(caKey); + X509_free(leaf); + X509_free(root); + X509_free(srvCert); + X509_free(caCert); + NCONF_free(conf); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_X509V3_EXT_bc(void) { EXPECT_DECLS; diff --git a/tests/api/test_ossl_x509_ext.h b/tests/api/test_ossl_x509_ext.h index a95b34a8050..2549286bad5 100644 --- a/tests/api/test_ossl_x509_ext.h +++ b/tests/api/test_ossl_x509_ext.h @@ -43,6 +43,7 @@ int test_wolfSSL_X509_EXTENSION_create_by_OBJ(void); int test_wolfSSL_X509V3_set_ctx(void); int test_wolfSSL_X509V3_EXT_get(void); int test_wolfSSL_X509V3_EXT_nconf(void); +int test_wolfSSL_X509V3_EXT_add_nconf(void); int test_wolfSSL_X509V3_EXT_bc(void); int test_wolfSSL_X509_get_ext_d2i_basic_constraints(void); int test_wolfSSL_X509V3_EXT_san(void); @@ -83,6 +84,7 @@ int test_wolfSSL_X509_set_ext_oid_collision(void); TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_set_ctx), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_get), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_nconf), \ + TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_add_nconf), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_bc), \ TEST_DECL_GROUP("ossl_x509_ext", \ test_wolfSSL_X509_get_ext_d2i_basic_constraints), \ diff --git a/tests/include.am b/tests/include.am index 7b3c3f5b3ea..1780dcb869a 100644 --- a/tests/include.am +++ b/tests/include.am @@ -96,6 +96,7 @@ EXTRA_DIST += tests/unit.h \ tests/test-ecc-cust-curves.conf \ tests/test-sm2.conf \ tests/NCONF_test.cnf \ + tests/X509V3_EXT_nconf_test.cnf \ tests/test-tls-downgrade.conf \ tests/TXT_DB.txt \ tests/utils.h \ diff --git a/wolfssl/openssl/x509v3.h b/wolfssl/openssl/x509v3.h index a991bd2abe7..39fbaac4e8a 100644 --- a/wolfssl/openssl/x509v3.h +++ b/wolfssl/openssl/x509v3.h @@ -162,6 +162,8 @@ WOLFSSL_API int wolfSSL_X509V3_EXT_print(WOLFSSL_BIO *out, WOLFSSL_X509_EXTENSION *ext, unsigned long flag, int indent); WOLFSSL_API int wolfSSL_X509V3_EXT_add_nconf(WOLFSSL_CONF *conf, WOLFSSL_X509V3_CTX *ctx, const char *section, WOLFSSL_X509 *cert); +WOLFSSL_API void wolfSSL_X509V3_set_nconf(WOLFSSL_X509V3_CTX *ctx, + WOLFSSL_CONF *conf); WOLFSSL_API WOLFSSL_X509_EXTENSION* wolfSSL_X509v3_get_ext( const WOLF_STACK_OF(WOLFSSL_X509_EXTENSION)* sk, int loc); WOLFSSL_API int wolfSSL_X509v3_get_ext_by_NID( @@ -263,8 +265,13 @@ typedef struct WOLFSSL_NAME_CONSTRAINTS NAME_CONSTRAINTS; #define X509V3_EXT_print wolfSSL_X509V3_EXT_print #define X509V3_EXT_conf_nid wolfSSL_X509V3_EXT_conf_nid #define X509V3_set_ctx wolfSSL_X509V3_set_ctx -#ifndef NO_WOLFSSL_STUB +#if defined(OPENSSL_ALL) && defined(WOLFSSL_CERT_GEN) && \ + defined(WOLFSSL_CERT_REQ) && defined(WOLFSSL_CERT_EXT) +#define X509V3_set_nconf wolfSSL_X509V3_set_nconf +#elif !defined(NO_WOLFSSL_STUB) #define X509V3_set_nconf(ctx, conf) WC_DO_NOTHING +#endif +#ifndef NO_WOLFSSL_STUB #define X509V3_EXT_cleanup() WC_DO_NOTHING #endif #define X509V3_set_ctx_test(ctx) wolfSSL_X509V3_set_ctx(ctx, NULL, NULL, NULL, NULL, CTX_TEST) diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 24ebab8000d..38929b30ae3 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -528,6 +528,11 @@ struct WOLFSSL_ACCESS_DESCRIPTION { struct WOLFSSL_X509V3_CTX { WOLFSSL_X509* x509; + /* Set by wolfSSL_X509V3_set_ctx(). Not owned by the context. */ + WOLFSSL_X509* issuer_cert; + WOLFSSL_X509* subject_cert; + /* Set by wolfSSL_X509V3_set_nconf(). Not owned by the context. */ + struct WOLFSSL_CONF* conf; }; struct WOLFSSL_ASN1_OBJECT { From 39a8520b206a52be0ef47dbc1e8f9d8f874d0386 Mon Sep 17 00:00:00 2001 From: Takashi Kojo Date: Sat, 31 Oct 2026 19:37:37 +0900 Subject: [PATCH 2/2] Fix X509V3_EXT_add_nconf criticality, SKID hash, EKU and SAN getters --- src/x509.c | 163 +++++++++++++++++++++++++++----- tests/X509V3_EXT_nconf_test.cnf | 17 +++- tests/api/test_ossl_x509_ext.c | 109 ++++++++++++++++++++- 3 files changed, 262 insertions(+), 27 deletions(-) diff --git a/src/x509.c b/src/x509.c index eca8acda65a..088e0af2e44 100644 --- a/src/x509.c +++ b/src/x509.c @@ -12908,6 +12908,7 @@ WOLF_STACK_OF(WOLFSSL_X509_OBJECT)* wolfSSL_sk_X509_OBJECT_deep_copy( if (ret == WOLFSSL_SUCCESS) { cert->altNamesSz = FlattenAltNames(cert->altNames, sizeof(cert->altNames), req->altNames); + cert->altNamesCrit = req->subjAltNameCrit; } #endif /* WOLFSSL_ALT_NAMES */ } @@ -12979,6 +12980,7 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) #ifdef WOLFSSL_ALT_NAMES cert->altNamesSz = FlattenAltNames(cert->altNames, sizeof(cert->altNames), x509->altNames); + cert->altNamesCrit = x509->subjAltNameCrit; #endif /* WOLFSSL_ALT_NAMES */ cert->sigType = wolfSSL_X509_get_signature_type(x509); @@ -17853,6 +17855,9 @@ int wolfSSL_X509_set_subject_key_id(WOLFSSL_X509* x509, XMEMCPY(x509->subjKeyId, skid, (word32)skidSz); x509->subjKeyIdSz = (word32)skidSz; x509->subjKeyIdSet = 1; + /* Cached ASN1_STRING handed out by get0_subject_key_id() is stale. */ + wolfSSL_ASN1_STRING_free(x509->subjKeyIdStr); + x509->subjKeyIdStr = NULL; return WOLFSSL_SUCCESS; } @@ -18169,6 +18174,44 @@ static int x509v3_conf_bool(const char* str) return -1; } +#ifndef NO_SHA +/* Find the subjectPublicKey BIT STRING value in a SubjectPublicKeyInfo: + * SEQUENCE { AlgorithmIdentifier, BIT STRING }. The whole buffer must be the + * SEQUENCE and the algorithm must be keyOID. + * + * Returns 0 on success or -1 when der is not such a SubjectPublicKeyInfo. + */ +static int x509v3_spki_pubkey(const byte* der, word32 derSz, word32 keyOID, + const byte** key, word32* keySz) +{ + word32 idx = 0; + word32 algEnd; + word32 oid = 0; + int len; + + if (GetSequence(der, &idx, &len, derSz) < 0 || + idx + (word32)len != derSz) { + return -1; + } + if (GetSequence(der, &idx, &len, derSz) < 0) { + return -1; + } + algEnd = idx + (word32)len; + if (GetObjectId(der, &idx, &oid, oidKeyType, algEnd) < 0 || + oid != keyOID) { + return -1; + } + idx = algEnd; + if (CheckBitString(der, &idx, &len, derSz, 1, NULL) < 0 || + idx + (word32)len != derSz) { + return -1; + } + *key = der + idx; + *keySz = (word32)len; + return 0; +} +#endif /* !NO_SHA */ + /* Calculate the key identifier of a certificate's public key as described in * RFC 5280, 4.2.1.2 (1): SHA-1 of the subjectPublicKey BIT STRING value. * This is the value OpenSSL uses for "subjectKeyIdentifier = hash". @@ -18181,32 +18224,29 @@ static int x509v3_conf_bool(const char* str) static int x509v3_pubkey_keyid(const WOLFSSL_X509* x509, byte* hash) { #ifndef NO_SHA - const byte* der; - word32 idx = 0; - word32 maxIdx; - int len; + const byte* key; + word32 keySz; if (x509 == NULL || x509->pubKey.buffer == NULL || x509->pubKey.length == 0) { WOLFSSL_MSG("Public key not set"); return WOLFSSL_FAILURE; } - der = x509->pubKey.buffer; - maxIdx = x509->pubKey.length; - /* SubjectPublicKeyInfo: SEQUENCE { AlgorithmIdentifier, BIT STRING } */ - if (GetSequence(der, &idx, &len, maxIdx) < 0) { - return WOLFSSL_FAILURE; - } - maxIdx = idx + (word32)len; - if (GetSequence(der, &idx, &len, maxIdx) < 0) { - return WOLFSSL_FAILURE; - } - idx += (word32)len; - if (CheckBitString(der, &idx, &len, maxIdx, 1, NULL) < 0) { - return WOLFSSL_FAILURE; + /* The key is kept as a SubjectPublicKeyInfo (wolfSSL_X509_set_pubkey() + * with RSA or ECC, or a parsed ECC certificate) or as only the + * subjectPublicKey value (RSAPublicKey of a parsed RSA certificate, raw + * key of Ed25519, ML-DSA and others). */ + if (x509v3_spki_pubkey(x509->pubKey.buffer, x509->pubKey.length, + (word32)x509->pubKeyOID, &key, &keySz) != 0) { + if (x509->pubKeyOID == ECDSAk) { + WOLFSSL_MSG("Bad ECC SubjectPublicKeyInfo"); + return WOLFSSL_FAILURE; + } + key = x509->pubKey.buffer; + keySz = x509->pubKey.length; } - if (wc_ShaHash(der + idx, (word32)len, hash) != 0) { + if (wc_ShaHash(key, keySz, hash) != 0) { return WOLFSSL_FAILURE; } return WOLFSSL_SUCCESS; @@ -18512,6 +18552,66 @@ static int x509v3_conf_san(WOLFSSL_CONF* conf, WOLFSSL_X509* cert, return ret; } +/* extendedKeyUsage = name[,name...] + * Sets the usage bits that are encoded and the list of KeyPurposeId OIDs that + * wolfSSL_X509_get_ext_d2i() returns, as for a parsed certificate. */ +static int x509v3_conf_eku(WOLFSSL_X509* cert, const char* value, int crit) +{ + /* anyExtendedKeyUsage: 2.5.29.37.0 */ + static const byte anyOid[] = { 0x06, 0x04, 0x55, 0x1d, 0x25, 0x00 }; + /* id-kp: 1.3.6.1.5.5.7.3, followed by one byte for the purpose. */ + static const byte kpOid[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03 + }; + static const struct { + byte bit; + byte id; + } kp[] = { + { EXTKEYUSE_SERVER_AUTH, 1 }, + { EXTKEYUSE_CLIENT_AUTH, 2 }, + { EXTKEYUSE_CODESIGN, 3 }, + { EXTKEYUSE_EMAILPROT, 4 }, + { EXTKEYUSE_TIMESTAMP, 8 }, + { EXTKEYUSE_OCSP_SIGN, 9 }, + }; + byte usage = 0; + byte* der; + word32 derSz = 0; + word32 count = 0; + size_t i; + + if (ParseExtKeyUsageStr(value, &usage, cert->heap) != 0) { + return WOLFSSL_FAILURE; + } + der = (byte*)XMALLOC(sizeof(anyOid) + + (sizeof(kp) / sizeof(kp[0])) * (sizeof(kpOid) + 1), cert->heap, + DYNAMIC_TYPE_X509_EXT); + if (der == NULL) { + return WOLFSSL_FAILURE; + } + if (usage & EXTKEYUSE_ANY) { + XMEMCPY(der, anyOid, sizeof(anyOid)); + derSz += (word32)sizeof(anyOid); + count++; + } + for (i = 0; i < sizeof(kp) / sizeof(kp[0]); i++) { + if (usage & kp[i].bit) { + XMEMCPY(der + derSz, kpOid, sizeof(kpOid)); + derSz += (word32)sizeof(kpOid); + der[derSz++] = kp[i].id; + count++; + } + } + + XFREE(cert->extKeyUsageSrc, cert->heap, DYNAMIC_TYPE_X509_EXT); + cert->extKeyUsageSrc = der; + cert->extKeyUsageSz = derSz; + cert->extKeyUsageCount = count; + cert->extKeyUsage = usage; + cert->extKeyUsageCrit = (byte)crit; + return WOLFSSL_SUCCESS; +} + /* Apply one "name = value" extension entry to the certificate. */ static int x509v3_conf_add_value(WOLFSSL_CONF* conf, WOLFSSL_X509V3_CTX* ctx, WOLFSSL_X509* cert, const char* name, const char* value) @@ -18539,12 +18639,26 @@ static int x509v3_conf_add_value(WOLFSSL_CONF* conf, WOLFSSL_X509V3_CTX* ctx, v += (v[8] == ',') ? 9 : 8; } + /* The certificate encoder always marks keyUsage critical, and + * extendedKeyUsage and the key identifiers not critical. Reject anything + * else rather than produce a certificate that differs from the + * configuration. */ + if ((nid == WC_NID_key_usage && !crit) || + (crit && (nid == WC_NID_ext_key_usage || + nid == WC_NID_subject_key_identifier || + nid == WC_NID_authority_key_identifier))) { + WOLFSSL_MSG("Unsupported extension criticality"); + nid = WC_NID_undef; + } + switch (nid) { + case WC_NID_undef: + /* Rejected above. */ + break; case WC_NID_basic_constraints: ret = x509v3_conf_basic_constraints(cert, v, crit); break; case WC_NID_key_usage: - /* Always encoded as critical. */ if (ParseKeyUsageStr(v, &cert->keyUsage, cert->heap) == 0) { cert->keyUsageCrit = (byte)crit; cert->keyUsageSet = 1; @@ -18552,11 +18666,7 @@ static int x509v3_conf_add_value(WOLFSSL_CONF* conf, WOLFSSL_X509V3_CTX* ctx, } break; case WC_NID_ext_key_usage: - /* Always encoded as non-critical. */ - if (ParseExtKeyUsageStr(v, &cert->extKeyUsage, cert->heap) == 0) { - cert->extKeyUsageCrit = (byte)crit; - ret = WOLFSSL_SUCCESS; - } + ret = x509v3_conf_eku(cert, v, crit); break; case WC_NID_subject_key_identifier: ret = x509v3_conf_skid(ctx, cert, v, crit); @@ -18565,8 +18675,11 @@ static int x509v3_conf_add_value(WOLFSSL_CONF* conf, WOLFSSL_X509V3_CTX* ctx, ret = x509v3_conf_akid(ctx, cert, v, crit); break; case WC_NID_subject_alt_name: - /* Criticality is not stored for subjectAltName. */ ret = x509v3_conf_san(conf, cert, v); + if (ret == WOLFSSL_SUCCESS && cert->altNames != NULL) { + cert->subjAltNameSet = 1; + cert->subjAltNameCrit = (byte)(crit != 0); + } break; default: WOLFSSL_MSG("Unsupported extension in configuration"); diff --git a/tests/X509V3_EXT_nconf_test.cnf b/tests/X509V3_EXT_nconf_test.cnf index 4ce405553f2..140fa0c70eb 100644 --- a/tests/X509V3_EXT_nconf_test.cnf +++ b/tests/X509V3_EXT_nconf_test.cnf @@ -14,7 +14,7 @@ keyUsage = critical, digitalSignature extendedKeyUsage = serverAuth, clientAuth subjectKeyIdentifier = 01:02:03:04 authorityKeyIdentifier = keyid:always -subjectAltName = DNS:www.example.com, @alt_names +subjectAltName = critical, DNS:www.example.com, @alt_names [ alt_names ] DNS.1 = a.example.com @@ -47,3 +47,18 @@ subjectAltName = IP:999.1.1.1 [ bad_san_partial ] subjectAltName = DNS:added.example.com, URI: + +[ skid_hash ] +subjectKeyIdentifier = hash + +[ bad_ku_noncrit ] +keyUsage = digitalSignature + +[ bad_eku_crit ] +extendedKeyUsage = critical, serverAuth + +[ bad_skid_crit ] +subjectKeyIdentifier = critical, hash + +[ bad_akid_crit ] +authorityKeyIdentifier = critical, keyid diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index 28b37b3f5f4..ef0c275178b 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -1230,6 +1230,59 @@ int test_wolfSSL_X509V3_EXT_nconf(void) return EXPECT_RESULT(); } +#if defined(OPENSSL_ALL) && defined(WOLFSSL_CERT_GEN) && \ + defined(WOLFSSL_CERT_REQ) && defined(WOLFSSL_CERT_EXT) && \ + !defined(NO_FILESYSTEM) && !defined(NO_BIO) && defined(HAVE_ECC) && \ + !defined(NO_SHA) && !defined(NO_SHA256) +/* Check "subjectKeyIdentifier = hash" gives the subject key identifier that + * OpenSSL made the same way for the certificate in file. The public key is + * either the one parsed from the file or one set with X509_set_pubkey(). */ +static int test_X509V3_EXT_add_nconf_skid_hash(CONF* conf, const char* file, + int setPubKey) +{ + EXPECT_DECLS; + X509* ref = NULL; + X509* x509 = NULL; + EVP_PKEY* pub = NULL; + const ASN1_STRING* skid = NULL; + byte expSkid[CTC_MAX_SKID_SIZE]; + int expSkidSz = 0; + + ExpectNotNull(ref = wolfSSL_X509_load_certificate_file(file, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(skid = X509_get0_subject_key_id(ref)); + ExpectIntLE(expSkidSz = ASN1_STRING_length(skid), (int)sizeof(expSkid)); + if (EXPECT_SUCCESS()) { + XMEMCPY(expSkid, ASN1_STRING_get0_data(skid), (size_t)expSkidSz); + } + if (setPubKey) { + ExpectNotNull(x509 = X509_new()); + ExpectNotNull(pub = X509_get_pubkey(ref)); + ExpectIntEQ(X509_set_pubkey(x509, pub), WOLFSSL_SUCCESS); + EVP_PKEY_free(pub); + } + else { + x509 = ref; + ref = NULL; + } + + /* Start without a key identifier so the result is the computed one. */ + if (EXPECT_SUCCESS() && x509 != NULL && x509->subjKeyId != NULL) { + XMEMSET(x509->subjKeyId, 0, x509->subjKeyIdSz); + x509->subjKeyIdSet = 0; + } + ExpectIntEQ(X509V3_EXT_add_nconf(conf, NULL, "skid_hash", x509), + WOLFSSL_SUCCESS); + ExpectNotNull(skid = X509_get0_subject_key_id(x509)); + ExpectIntEQ(ASN1_STRING_length(skid), expSkidSz); + ExpectBufEQ(ASN1_STRING_get0_data(skid), expSkid, expSkidSz); + + X509_free(x509); + X509_free(ref); + return EXPECT_RESULT(); +} +#endif + int test_wolfSSL_X509V3_EXT_add_nconf(void) { EXPECT_DECLS; @@ -1259,6 +1312,8 @@ int test_wolfSSL_X509V3_EXT_add_nconf(void) int derSz = 0; DNS_entry* dns = NULL; int sanCnt = 0; + int crit = -1; + WOLF_STACK_OF(WOLFSSL_ASN1_OBJECT)* eku = NULL; ExpectNotNull(conf = NCONF_new(NULL)); ExpectIntEQ(NCONF_load(conf, confFile, &eline), 1); @@ -1322,6 +1377,19 @@ int test_wolfSSL_X509V3_EXT_add_nconf(void) wolfSSL_X509_free(ctx.x509); ctx.x509 = NULL; + /* Extensions can be read back before signing. */ + ExpectNotNull(eku = (WOLF_STACK_OF(WOLFSSL_ASN1_OBJECT)*)X509_get_ext_d2i( + leaf, NID_ext_key_usage, NULL, NULL)); + ExpectIntEQ(sk_ASN1_OBJECT_num(eku), 2); + wolfSSL_sk_ASN1_OBJECT_pop_free(eku, NULL); + eku = NULL; + ExpectIntEQ(wolfSSL_X509_ext_isSet_by_NID(leaf, NID_subject_alt_name), 1); + ExpectNotNull(names = (GENERAL_NAMES*)X509_get_ext_d2i(leaf, + NID_subject_alt_name, NULL, NULL)); + ExpectIntEQ(sk_GENERAL_NAME_num(names), 4); + GENERAL_NAMES_free(names); + names = NULL; + ExpectNotNull(bio = BIO_new_file("./certs/ca-ecc-key.pem", "rb")); ExpectNotNull(caKey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL)); BIO_free(bio); @@ -1332,11 +1400,22 @@ int test_wolfSSL_X509V3_EXT_add_nconf(void) ExpectNotNull(decoded = d2i_X509(NULL, &p, derSz)); ExpectNotNull(bc = (BASIC_CONSTRAINTS*)X509_get_ext_d2i(decoded, - NID_basic_constraints, NULL, NULL)); + NID_basic_constraints, &crit, NULL)); + ExpectIntEQ(crit, 0); if (bc != NULL) { ExpectIntEQ(bc->ca, 0); } BASIC_CONSTRAINTS_free(bc); + /* Criticality is as configured after signing and parsing. */ + crit = -1; + ASN1_STRING_free((ASN1_STRING*)X509_get_ext_d2i(decoded, NID_key_usage, + &crit, NULL)); + ExpectIntEQ(crit, 1); + /* X509_get_ext_d2i() gives -2 for criticality with more than one + * KeyPurposeId, so check the parsed flag. */ + if (EXPECT_SUCCESS() && decoded != NULL) { + ExpectIntEQ(decoded->extKeyUsageCrit, 0); + } ExpectIntEQ(X509_get_key_usage(decoded), KU_DIGITAL_SIGNATURE); ExpectIntEQ(X509_get_extended_key_usage(decoded), XKU_SSL_SERVER | XKU_SSL_CLIENT); @@ -1348,6 +1427,10 @@ int test_wolfSSL_X509V3_EXT_add_nconf(void) ExpectBufEQ(decoded->authKeyId, ASN1_STRING_get0_data(expSkid), ASN1_STRING_length(expSkid)); } + /* As for extendedKeyUsage, the getter gives -2 with more than one name. */ + if (EXPECT_SUCCESS() && decoded != NULL) { + ExpectIntEQ(decoded->subjAltNameCrit, 1); + } ExpectNotNull(names = (GENERAL_NAMES*)X509_get_ext_d2i(decoded, NID_subject_alt_name, NULL, NULL)); ExpectIntEQ(sk_GENERAL_NAME_num(names), 4); @@ -1378,6 +1461,30 @@ int test_wolfSSL_X509V3_EXT_add_nconf(void) WOLFSSL_FAILURE); ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_ip", root), WOLFSSL_FAILURE); + /* Criticality the certificate encoder cannot produce. */ + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_ku_noncrit", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_eku_crit", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_skid_crit", root), + WOLFSSL_FAILURE); + ExpectIntEQ(X509V3_EXT_add_nconf(conf, &ctx, "bad_akid_crit", root), + WOLFSSL_FAILURE); + /* subjectKeyIdentifier = hash for each way a public key is held. */ + ExpectIntEQ(test_X509V3_EXT_add_nconf_skid_hash(conf, + "./certs/ca-ecc-cert.pem", 0), TEST_SUCCESS); +#ifndef NO_RSA + ExpectIntEQ(test_X509V3_EXT_add_nconf_skid_hash(conf, + "./certs/ca-cert.pem", 0), TEST_SUCCESS); + ExpectIntEQ(test_X509V3_EXT_add_nconf_skid_hash(conf, + "./certs/ca-cert.pem", 1), TEST_SUCCESS); +#endif +#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT) + ExpectIntEQ(test_X509V3_EXT_add_nconf_skid_hash(conf, + "./certs/ed25519/ca-ed25519.pem", 0), TEST_SUCCESS); + ExpectIntEQ(test_X509V3_EXT_add_nconf_skid_hash(conf, + "./certs/ed25519/ca-ed25519.pem", 1), TEST_SUCCESS); +#endif /* A failing subjectAltName value leaves the existing names unchanged. */ for (dns = (leaf != NULL) ? leaf->altNames : NULL; dns != NULL; dns = dns->next) {