From 653ec68ae7b4638d69eda14a8ee0e438b762a1cb Mon Sep 17 00:00:00 2001 From: Roy Carter Date: Fri, 22 May 2026 21:56:18 +0300 Subject: [PATCH 1/2] Introduce missing functionality that was found in compatibility layer of wolfssl --- src/pk_ec.c | 58 +++++++++++++++++ src/x509.c | 37 +++++++++++ tests/api.c | 145 ++++++++++++++++++++++++++++++++++++++++++ wolfssl/openssl/ec.h | 4 ++ wolfssl/openssl/ssl.h | 3 + wolfssl/ssl.h | 3 + 6 files changed, 250 insertions(+) diff --git a/src/pk_ec.c b/src/pk_ec.c index 883f758150c..bffbf31f929 100644 --- a/src/pk_ec.c +++ b/src/pk_ec.c @@ -4714,6 +4714,64 @@ int wolfSSL_EC_KEY_set_public_key(WOLFSSL_EC_KEY *key, return ret; } +/* + * Decode an octet-encoded EC public point into @key. + * + * Return code compliant with OpenSSL. + * + * @param [in, out] key EC key (must already have a group set). + * @param [in] buf Octet-encoded public point. + * @param [in] len Length of @buf in bytes. + * @param [in] ctx BN context. May be NULL. + * @return 1 on success. + * @return 0 on failure. + */ +int wolfSSL_EC_KEY_oct2key(WOLFSSL_EC_KEY *key, const unsigned char *buf, + size_t len, WOLFSSL_BN_CTX *ctx) +{ + int ret = 1; + const WOLFSSL_EC_GROUP *group = NULL; + WOLFSSL_EC_POINT *point = NULL; + + WOLFSSL_ENTER("wolfSSL_EC_KEY_oct2key"); + + if ((key == NULL) || (buf == NULL) || (len == 0)) { + WOLFSSL_MSG("wolfSSL_EC_KEY_oct2key Bad arguments"); + ret = 0; + } + + if (ret == 1) { + group = wolfSSL_EC_KEY_get0_group(key); + if (group == NULL) { + WOLFSSL_MSG("EC_KEY has no group set"); + ret = 0; + } + } + + if (ret == 1) { + point = wolfSSL_EC_POINT_new((WOLFSSL_EC_GROUP*)group); + if (point == NULL) { + WOLFSSL_MSG("wolfSSL_EC_POINT_new failed"); + ret = 0; + } + } + + if ((ret == 1) && + (wolfSSL_EC_POINT_oct2point(group, point, buf, len, ctx) != 1)) { + WOLFSSL_MSG("wolfSSL_EC_POINT_oct2point failed"); + ret = 0; + } + + if ((ret == 1) && (wolfSSL_EC_KEY_set_public_key(key, point) != 1)) { + WOLFSSL_MSG("wolfSSL_EC_KEY_set_public_key failed"); + ret = 0; + } + + wolfSSL_EC_POINT_free(point); + + return ret; +} + #ifndef NO_WOLFSSL_STUB /* Set the ASN.1 encoding flag against the EC key. * diff --git a/src/x509.c b/src/x509.c index c6998d35140..1d81a81fb74 100644 --- a/src/x509.c +++ b/src/x509.c @@ -3573,6 +3573,43 @@ WOLFSSL_X509_EXTENSION *wolfSSL_X509V3_EXT_i2d(int nid, int crit, return NULL; } +/** + * Encode @value as an extension of type @nid and append it to @x. + * + * The @flags argument (X509V3_ADD_DEFAULT / APPEND / REPLACE / KEEP_EXISTING + * in OpenSSL) is not supported here; non-zero values are treated as the + * default append behavior. + * + * @return WOLFSSL_SUCCESS on success, WOLFSSL_FAILURE otherwise. + */ +int wolfSSL_X509_add1_ext_i2d(WOLFSSL_X509 *x, int nid, void *value, + int crit, unsigned long flags) +{ + WOLFSSL_X509_EXTENSION *ext = NULL; + int ret; + + WOLFSSL_ENTER("wolfSSL_X509_add1_ext_i2d"); + + if (x == NULL || value == NULL) { + WOLFSSL_MSG("Bad parameter"); + return WOLFSSL_FAILURE; + } + + if (flags != 0) { + WOLFSSL_MSG("X509V3_ADD_* flags not supported; using default behavior"); + } + + ext = wolfSSL_X509V3_EXT_i2d(nid, crit, value); + if (ext == NULL) { + return WOLFSSL_FAILURE; + } + + ret = wolfSSL_X509_add_ext(x, ext, -1); + wolfSSL_X509_EXTENSION_free(ext); + + return ret; +} + /* Returns pointer to ASN1_OBJECT from an X509_EXTENSION object */ WOLFSSL_ASN1_OBJECT* wolfSSL_X509_EXTENSION_get_object( WOLFSSL_X509_EXTENSION* ext) diff --git a/tests/api.c b/tests/api.c index 6208cc2b2dd..e7b701a364c 100644 --- a/tests/api.c +++ b/tests/api.c @@ -2311,6 +2311,27 @@ static int test_wolfSSL_set_cipher_list_tls13_with_version(void) return EXPECT_RESULT(); } +/* Test SSL_set_ciphersuites OpenSSL-compat macro. */ +static int test_wolfSSL_SSL_set_ciphersuites(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_AESGCM) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = SSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = SSL_new(ctx)); + + ExpectIntEQ(SSL_set_ciphersuites(ssl, "TLS_AES_128_GCM_SHA256"), 1); + ExpectIntEQ(SSL_set_ciphersuites(ssl, "BOGUS-SUITE"), 0); + + SSL_free(ssl); + SSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_set_alpn_protos_default_fails(void) { EXPECT_DECLS; @@ -18934,6 +18955,25 @@ static int test_wolfSSL_sk_GENERAL_NAME(void) return EXPECT_RESULT(); } +static int test_wolfSSL_sk_GENERAL_NAME_new_null(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_CERTS) + STACK_OF(GENERAL_NAME)* sk = NULL; + GENERAL_NAME* gn = NULL; + + ExpectNotNull(sk = sk_GENERAL_NAME_new_null()); + ExpectIntEQ(sk_GENERAL_NAME_num(sk), 0); + + ExpectNotNull(gn = GENERAL_NAME_new()); + ExpectIntEQ(sk_GENERAL_NAME_push(sk, gn), 1); + ExpectIntEQ(sk_GENERAL_NAME_num(sk), 1); + + sk_GENERAL_NAME_pop_free(sk, GENERAL_NAME_free); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_GENERAL_NAME_print(void) { EXPECT_DECLS; @@ -19640,6 +19680,68 @@ static int test_wolfSSL_X509_set_extensions(void) return EXPECT_RESULT(); } +/* Test wolfSSL_X509_add1_ext_i2d using a SAN DNS entry. */ +static int test_wolfSSL_X509_add1_ext_i2d(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(OPENSSL_ALL) && !defined(NO_CERTS) && \ + !defined(NO_ASN) + WOLFSSL_X509* x509 = NULL; + WOLFSSL_GENERAL_NAMES* gns = NULL; + WOLFSSL_GENERAL_NAME* gn = NULL; + WOLFSSL_ASN1_STRING* dnsStr = NULL; + const char dns[] = "example.com"; + + ExpectNotNull(x509 = wolfSSL_X509_new()); + ExpectNotNull(gn = wolfSSL_GENERAL_NAME_new()); + ExpectNotNull(dnsStr = wolfSSL_ASN1_STRING_new()); + ExpectIntEQ(wolfSSL_ASN1_STRING_set(dnsStr, dns, (int)XSTRLEN(dns)), 1); + if (gn != NULL) { + wolfSSL_GENERAL_NAME_set0_value(gn, GEN_DNS, dnsStr); + dnsStr = NULL; + } + ExpectNotNull(gns = wolfSSL_sk_GENERAL_NAME_new(NULL)); + ExpectIntEQ(wolfSSL_sk_GENERAL_NAME_push(gns, gn), 1); + if (EXPECT_FAIL() && gn != NULL) { + wolfSSL_GENERAL_NAME_free(gn); + } + + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(NULL, NID_subject_alt_name, gns, 0, + 0), WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, NULL, 0, + 0), WOLFSSL_FAILURE); + + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + 0), WOLFSSL_SUCCESS); + + { + WOLFSSL_GENERAL_NAMES* readBack = NULL; + WOLFSSL_GENERAL_NAME* rbGn = NULL; + + ExpectNotNull(readBack = (WOLFSSL_GENERAL_NAMES*) + wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, + NULL)); + ExpectIntEQ(wolfSSL_sk_GENERAL_NAME_num(readBack), 1); + ExpectNotNull(rbGn = wolfSSL_sk_GENERAL_NAME_value(readBack, 0)); + if (rbGn != NULL) { + ExpectIntEQ(rbGn->type, GEN_DNS); + ExpectNotNull(rbGn->d.dNSName); + if (rbGn->d.dNSName != NULL) { + ExpectIntEQ(rbGn->d.dNSName->length, (int)XSTRLEN(dns)); + ExpectIntEQ(XMEMCMP(rbGn->d.dNSName->data, dns, + XSTRLEN(dns)), 0); + } + } + wolfSSL_sk_GENERAL_NAME_pop_free(readBack, wolfSSL_GENERAL_NAME_free); + } + + wolfSSL_sk_GENERAL_NAME_pop_free(gns, wolfSSL_GENERAL_NAME_free); + wolfSSL_ASN1_STRING_free(dnsStr); + wolfSSL_X509_free(x509); +#endif + return EXPECT_RESULT(); +} + /* Round trip test for wolfSSL_X509_set_authority_key_id() with a raw key ID. * * Builds a cert, calls the setter with a 20-byte raw keyId, signs the cert, @@ -20621,6 +20723,45 @@ static int test_wolfSSL_d2i_and_i2d_PublicKey_ecc(void) return EXPECT_RESULT(); } +/* Round-trip test for EC_KEY_oct2key with a P-256 public point. */ +static int test_wolfSSL_EC_KEY_oct2key(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_ECC) && !defined(NO_ASN) + EC_KEY* src = NULL; + EC_KEY* dst = NULL; + const EC_GROUP* group = NULL; + const EC_POINT* src_pub = NULL; + const EC_POINT* dst_pub = NULL; + unsigned char buf[1 + 2 * 32]; + size_t enc_len = 0; + + ExpectNotNull(src = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + ExpectIntEQ(EC_KEY_generate_key(src), 1); + ExpectNotNull(group = EC_KEY_get0_group(src)); + ExpectNotNull(src_pub = EC_KEY_get0_public_key(src)); + + enc_len = EC_POINT_point2oct(group, src_pub, + POINT_CONVERSION_UNCOMPRESSED, buf, sizeof(buf), NULL); + ExpectIntEQ((int)enc_len, (int)sizeof(buf)); + + ExpectNotNull(dst = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + + ExpectIntEQ(EC_KEY_oct2key(NULL, buf, enc_len, NULL), 0); + ExpectIntEQ(EC_KEY_oct2key(dst, NULL, enc_len, NULL), 0); + ExpectIntEQ(EC_KEY_oct2key(dst, buf, 0, NULL), 0); + + ExpectIntEQ(EC_KEY_oct2key(dst, buf, enc_len, NULL), 1); + + ExpectNotNull(dst_pub = EC_KEY_get0_public_key(dst)); + ExpectIntEQ(EC_POINT_cmp(group, src_pub, dst_pub, NULL), 0); + + EC_KEY_free(dst); + EC_KEY_free(src); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_d2i_and_i2d_DSAparams(void) { EXPECT_DECLS; @@ -34670,6 +34811,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_d2i_and_i2d_PublicKey), TEST_DECL(test_wolfSSL_d2i_and_i2d_PublicKey_ecc), + TEST_DECL(test_wolfSSL_EC_KEY_oct2key), #ifndef NO_BIO TEST_DECL(test_wolfSSL_d2i_PUBKEY), #endif @@ -34717,6 +34859,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_X509_ALGOR_get0), TEST_DECL(test_wolfSSL_X509_SEP), TEST_DECL(test_wolfSSL_X509_set_extensions), + TEST_DECL(test_wolfSSL_X509_add1_ext_i2d), TEST_DECL(test_wolfSSL_X509_set_authority_key_id_roundtrip), TEST_DECL(test_wolfSSL_X509_set_authority_key_id_ex_roundtrip), TEST_DECL(test_wolfSSL_X509_set_authority_key_id_overwrite), @@ -34803,6 +34946,7 @@ TEST_CASE testCases[] = { /* Can't memory test as tcp_connect aborts. */ TEST_DECL(test_wolfSSL_d2i_SSL_SESSION_bounds_check), TEST_DECL(test_wolfSSL_sk_GENERAL_NAME), + TEST_DECL(test_wolfSSL_sk_GENERAL_NAME_new_null), TEST_DECL(test_wolfSSL_GENERAL_NAME_print), TEST_DECL(test_wolfSSL_sk_DIST_POINT), TEST_DECL(test_wolfSSL_verify_mode), @@ -34961,6 +35105,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_set_cipher_list_tls13_keeps_tls12), TEST_DECL(test_wolfSSL_set_cipher_list_tls12_with_version), TEST_DECL(test_wolfSSL_set_cipher_list_tls13_with_version), + TEST_DECL(test_wolfSSL_SSL_set_ciphersuites), TEST_DECL(test_wolfSSL_set_alpn_protos_default_fails), TEST_DECL(test_wolfSSL_CTX_use_certificate), TEST_DECL(test_wolfSSL_CTX_use_certificate_file), diff --git a/wolfssl/openssl/ec.h b/wolfssl/openssl/ec.h index c706761e089..95e32972fa4 100644 --- a/wolfssl/openssl/ec.h +++ b/wolfssl/openssl/ec.h @@ -295,6 +295,9 @@ void wolfSSL_EC_KEY_set_asn1_flag(WOLFSSL_EC_KEY *key, int asn1_flag); WOLFSSL_API int wolfSSL_EC_KEY_set_public_key(WOLFSSL_EC_KEY *key, const WOLFSSL_EC_POINT *pub); +WOLFSSL_API +int wolfSSL_EC_KEY_oct2key(WOLFSSL_EC_KEY *key, const unsigned char *buf, + size_t len, WOLFSSL_BN_CTX *ctx); WOLFSSL_API int wolfSSL_EC_KEY_check_key(const WOLFSSL_EC_KEY *key); #if !defined(NO_FILESYSTEM) && !defined(NO_STDIO_FILESYSTEM) WOLFSSL_API int wolfSSL_EC_KEY_print_fp(XFILE fp, WOLFSSL_EC_KEY* key, @@ -491,6 +494,7 @@ typedef WOLFSSL_EC_KEY_METHOD EC_KEY_METHOD; #define ECPoint_d2i wolfSSL_ECPoint_d2i #define EC_POINT_point2oct wolfSSL_EC_POINT_point2oct #define EC_POINT_oct2point wolfSSL_EC_POINT_oct2point +#define EC_KEY_oct2key wolfSSL_EC_KEY_oct2key #define EC_POINT_point2bn wolfSSL_EC_POINT_point2bn #define EC_POINT_is_on_curve wolfSSL_EC_POINT_is_on_curve #define o2i_ECPublicKey wolfSSL_o2i_ECPublicKey diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 063500675e1..284322a7a32 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -394,6 +394,7 @@ typedef STACK_OF(ACCESS_DESCRIPTION) AUTHORITY_INFO_ACCESS; #define SSL_CTX_set_cipher_list wolfSSL_CTX_set_cipher_list #define SSL_CTX_set_ciphersuites wolfSSL_CTX_set_cipher_list #define SSL_set_cipher_list wolfSSL_set_cipher_list +#define SSL_set_ciphersuites wolfSSL_set_cipher_list /* wolfSSL does not support security levels */ #define SSL_CTX_set_security_level wolfSSL_CTX_set_security_level #define SSL_CTX_get_security_level wolfSSL_CTX_get_security_level @@ -617,6 +618,7 @@ typedef STACK_OF(ACCESS_DESCRIPTION) AUTHORITY_INFO_ACCESS; #define X509_check_issued wolfSSL_X509_check_issued #define X509_dup wolfSSL_X509_dup #define X509_add_ext wolfSSL_X509_add_ext +#define X509_add1_ext_i2d wolfSSL_X509_add1_ext_i2d #define X509_delete_ext wolfSSL_X509_delete_ext #define X509_get0_subject_key_id wolfSSL_X509_get0_subject_key_id @@ -1597,6 +1599,7 @@ typedef WOLFSSL_SRTP_PROTECTION_PROFILE SRTP_PROTECTION_PROFILE; #define SSL_SESSION_print wolfSSL_SESSION_print #define sk_GENERAL_NAME_pop_free wolfSSL_sk_GENERAL_NAME_pop_free #define sk_GENERAL_NAME_new wolfSSL_sk_GENERAL_NAME_new +#define sk_GENERAL_NAME_new_null() wolfSSL_sk_GENERAL_NAME_new(NULL) #define sk_GENERAL_NAME_free wolfSSL_sk_GENERAL_NAME_free #define sk_ASN1_OBJECT_pop_free wolfSSL_sk_ASN1_OBJECT_pop_free #define GENERAL_NAME_free wolfSSL_GENERAL_NAME_free diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 713c5a55c07..009b971b475 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -5259,6 +5259,9 @@ WOLFSSL_API int wolfSSL_X509_get_ext_by_NID(const WOLFSSL_X509 *x, int nid, int WOLFSSL_API int wolfSSL_X509_add_ext(WOLFSSL_X509 *x, WOLFSSL_X509_EXTENSION *ex, int loc); WOLFSSL_API WOLFSSL_X509_EXTENSION *wolfSSL_X509V3_EXT_i2d(int nid, int crit, void *data); +WOLFSSL_API int wolfSSL_X509_add1_ext_i2d(WOLFSSL_X509 *x, int nid, + void *value, int crit, + unsigned long flags); WOLFSSL_API WOLFSSL_X509_EXTENSION *wolfSSL_X509_delete_ext(WOLFSSL_X509 *x509, int loc); WOLFSSL_API WOLFSSL_X509_EXTENSION* wolfSSL_X509V3_EXT_conf_nid( WOLF_LHASH_OF(CONF_VALUE)* conf, WOLFSSL_X509V3_CTX* ctx, int nid, From 6a8f12ac1f08425689d036cc11734ff879290a70 Mon Sep 17 00:00:00 2001 From: Roy Carter Date: Sun, 7 Jun 2026 10:53:59 +0300 Subject: [PATCH 2/2] Refactor - Fix PR notes --- src/x509.c | 167 ++++++++++++++++++++++++-- tests/api.c | 247 ++++++++++++++++++++++++++++++++++++++- wolfssl/openssl/x509v3.h | 21 ++++ 3 files changed, 426 insertions(+), 9 deletions(-) diff --git a/src/x509.c b/src/x509.c index 1d81a81fb74..3d04b625aa3 100644 --- a/src/x509.c +++ b/src/x509.c @@ -1462,6 +1462,18 @@ int wolfSSL_X509_add_ext(WOLFSSL_X509 *x509, WOLFSSL_X509_EXTENSION *ext, return WOLFSSL_FAILURE; } } + else if (ext && ext->ext_sk != NULL) { + /* wolfSSL_X509V3_EXT_i2d() represents extKeyUsage as a stack of + * ASN1_OBJECTs in ext_sk, which cannot be mapped back to the + * x509->extKeyUsage bitmask here. Fail rather than silently + * report success without adding anything. */ + WOLFSSL_MSG("extKeyUsage object stack not supported by " + "wolfSSL_X509_add_ext"); + return WOLFSSL_FAILURE; + } + else { + return WOLFSSL_FAILURE; + } break; case WC_NID_basic_constraints: if (ext->obj) { @@ -3504,6 +3516,13 @@ WOLFSSL_X509_EXTENSION *wolfSSL_X509V3_EXT_i2d(int nid, int crit, goto err_cleanup; } + /* wolfSSL_X509_add_ext() routes on ext->obj->type; tag the object as + * basicConstraints so the extension is consumed instead of being + * rejected as type 0. */ + ext->obj->type = WC_NID_basic_constraints; + ext->obj->nid = WC_NID_basic_constraints; + ext->obj->grp = oidCertExtType; + ext->obj->ca = bc->ca; if (bc->pathlen) { ext->obj->pathlen = wolfSSL_ASN1_INTEGER_dup(bc->pathlen); @@ -3573,12 +3592,97 @@ WOLFSSL_X509_EXTENSION *wolfSSL_X509V3_EXT_i2d(int nid, int crit, return NULL; } +/* Return 1 if an extension of type @nid is already present in the in-memory + * @x509, based on wolfSSL's typed extension storage; 0 otherwise. Only the + * NIDs that wolfSSL_X509_add_ext() can consume are recognized. */ +static int wolfssl_x509_ext_is_set(const WOLFSSL_X509 *x509, int nid) +{ + switch (nid) { + case WC_NID_subject_alt_name: + return (x509->subjAltNameSet != 0) || (x509->altNames != NULL); + case WC_NID_key_usage: + return x509->keyUsageSet != 0; + case WC_NID_ext_key_usage: + return x509->extKeyUsage != 0; + case WC_NID_basic_constraints: + return x509->basicConstSet != 0; + case WC_NID_subject_key_identifier: + return x509->subjKeyId != NULL; + case WC_NID_authority_key_identifier: + return x509->authKeyId != NULL; + default: + return 0; + } +} + +/* Remove the extension of type @nid from the in-memory @x509 by clearing the + * matching typed storage (mirrors the frees in FreeX509()). Returns + * WOLFSSL_SUCCESS if @nid is a supported, removable extension, otherwise + * WOLFSSL_FAILURE. */ +static int wolfssl_x509_remove_ext(WOLFSSL_X509 *x509, int nid) +{ + switch (nid) { + case WC_NID_subject_alt_name: + if (x509->altNames != NULL) { + FreeAltNames(x509->altNames, x509->heap); + x509->altNames = NULL; + } + x509->subjAltNameSet = 0; + x509->subjAltNameCrit = 0; + break; + case WC_NID_key_usage: + x509->keyUsage = 0; + x509->keyUsageCrit = 0; + x509->keyUsageSet = 0; + break; + case WC_NID_ext_key_usage: + x509->extKeyUsage = 0; + x509->extKeyUsageCrit = 0; + break; + case WC_NID_basic_constraints: + x509->isCa = 0; + x509->pathLength = 0; + x509->basicConstCrit = 0; + x509->basicConstSet = 0; + x509->basicConstPlSet = 0; + x509->pathLengthSet = 0; + break; + case WC_NID_subject_key_identifier: + XFREE(x509->subjKeyId, x509->heap, DYNAMIC_TYPE_X509_EXT); + x509->subjKeyId = NULL; + x509->subjKeyIdSz = 0; + break; + case WC_NID_authority_key_identifier: + /* authKeyId may point into authKeyIdSrc; free the source first. */ + if (x509->authKeyIdSrc != NULL) { + XFREE(x509->authKeyIdSrc, x509->heap, DYNAMIC_TYPE_X509_EXT); + x509->authKeyIdSrc = NULL; + } + else { + XFREE(x509->authKeyId, x509->heap, DYNAMIC_TYPE_X509_EXT); + } + x509->authKeyId = NULL; + x509->authKeyIdSz = 0; + break; + default: + WOLFSSL_MSG("Extension NID not supported for removal"); + return WOLFSSL_FAILURE; + } + return WOLFSSL_SUCCESS; +} + /** - * Encode @value as an extension of type @nid and append it to @x. - * - * The @flags argument (X509V3_ADD_DEFAULT / APPEND / REPLACE / KEEP_EXISTING - * in OpenSSL) is not supported here; non-zero values are treated as the - * default append behavior. + * Encode @value as an extension of type @nid and add it to @x, honoring the + * OpenSSL X509V3_ADD_* operation selected by the low nibble of @flags: + * X509V3_ADD_DEFAULT - add, fail if already present + * X509V3_ADD_APPEND - add without checking for an existing one + * X509V3_ADD_REPLACE - replace any existing, else add + * X509V3_ADD_REPLACE_EXISTING - replace, fail if not already present + * X509V3_ADD_KEEP_EXISTING - keep existing (no-op if present), else add + * X509V3_ADD_DELETE - delete existing, fail if not present + * X509V3_ADD_SILENT suppresses the "already present" failure for the default + * operation. Note that extKeyUsage is not supported by the underlying + * wolfSSL_X509_add_ext() and will fail. * * @return WOLFSSL_SUCCESS on success, WOLFSSL_FAILURE otherwise. */ @@ -3587,16 +3691,63 @@ int wolfSSL_X509_add1_ext_i2d(WOLFSSL_X509 *x, int nid, void *value, { WOLFSSL_X509_EXTENSION *ext = NULL; int ret; + unsigned long op; + int exists; WOLFSSL_ENTER("wolfSSL_X509_add1_ext_i2d"); - if (x == NULL || value == NULL) { + if (x == NULL) { WOLFSSL_MSG("Bad parameter"); return WOLFSSL_FAILURE; } - if (flags != 0) { - WOLFSSL_MSG("X509V3_ADD_* flags not supported; using default behavior"); + op = flags & WOLFSSL_X509V3_ADD_OP_MASK; + exists = wolfssl_x509_ext_is_set(x, nid); + + switch (op) { + case WOLFSSL_X509V3_ADD_DELETE: + if (!exists) { + WOLFSSL_MSG("No extension to delete (X509V3_ADD_DELETE)"); + return WOLFSSL_FAILURE; + } + return wolfssl_x509_remove_ext(x, nid); + case WOLFSSL_X509V3_ADD_DEFAULT: + if (exists && ((flags & WOLFSSL_X509V3_ADD_SILENT) == 0)) { + WOLFSSL_MSG("Extension already present (X509V3_ADD_DEFAULT)"); + return WOLFSSL_FAILURE; + } + break; + case WOLFSSL_X509V3_ADD_APPEND: + break; + case WOLFSSL_X509V3_ADD_REPLACE: + if (exists && wolfssl_x509_remove_ext(x, nid) != WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } + break; + case WOLFSSL_X509V3_ADD_REPLACE_EXISTING: + if (!exists) { + WOLFSSL_MSG("No extension to replace " + "(X509V3_ADD_REPLACE_EXISTING)"); + return WOLFSSL_FAILURE; + } + if (wolfssl_x509_remove_ext(x, nid) != WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } + break; + case WOLFSSL_X509V3_ADD_KEEP_EXISTING: + if (exists) { + /* Keep the existing extension; nothing to add. */ + return WOLFSSL_SUCCESS; + } + break; + default: + WOLFSSL_MSG("Unsupported X509V3_ADD_* operation"); + return WOLFSSL_FAILURE; + } + + if (value == NULL) { + WOLFSSL_MSG("Bad parameter"); + return WOLFSSL_FAILURE; } ext = wolfSSL_X509V3_EXT_i2d(nid, crit, value); diff --git a/tests/api.c b/tests/api.c index e7b701a364c..c993697e441 100644 --- a/tests/api.c +++ b/tests/api.c @@ -18966,7 +18966,14 @@ static int test_wolfSSL_sk_GENERAL_NAME_new_null(void) ExpectIntEQ(sk_GENERAL_NAME_num(sk), 0); ExpectNotNull(gn = GENERAL_NAME_new()); - ExpectIntEQ(sk_GENERAL_NAME_push(sk, gn), 1); + if (gn != NULL) { + ExpectIntEQ(sk_GENERAL_NAME_push(sk, gn), 1); + /* On push failure the stack does not own gn; free it to avoid a leak. */ + if (EXPECT_FAIL()) { + GENERAL_NAME_free(gn); + gn = NULL; + } + } ExpectIntEQ(sk_GENERAL_NAME_num(sk), 1); sk_GENERAL_NAME_pop_free(sk, GENERAL_NAME_free); @@ -19742,6 +19749,241 @@ static int test_wolfSSL_X509_add1_ext_i2d(void) return EXPECT_RESULT(); } +#if defined(OPENSSL_EXTRA) && defined(OPENSSL_ALL) && !defined(NO_CERTS) && \ + !defined(NO_ASN) +/* Build a STACK_OF(GENERAL_NAME) holding a single GEN_DNS entry. Returns the + * stack (caller frees with wolfSSL_sk_GENERAL_NAME_pop_free) or NULL. */ +static WOLFSSL_GENERAL_NAMES* test_san_dns_stack(const char* dns) +{ + WOLFSSL_GENERAL_NAMES* gns = NULL; + WOLFSSL_GENERAL_NAME* gn = NULL; + WOLFSSL_ASN1_STRING* dnsStr = NULL; + + gn = wolfSSL_GENERAL_NAME_new(); + dnsStr = wolfSSL_ASN1_STRING_new(); + if ((gn == NULL) || (dnsStr == NULL)) { + wolfSSL_GENERAL_NAME_free(gn); + wolfSSL_ASN1_STRING_free(dnsStr); + return NULL; + } + if (wolfSSL_ASN1_STRING_set(dnsStr, dns, (int)XSTRLEN(dns)) != 1) { + wolfSSL_GENERAL_NAME_free(gn); + wolfSSL_ASN1_STRING_free(dnsStr); + return NULL; + } + /* set0 takes ownership of dnsStr. */ + wolfSSL_GENERAL_NAME_set0_value(gn, GEN_DNS, dnsStr); + + gns = wolfSSL_sk_GENERAL_NAME_new(NULL); + if (gns == NULL) { + wolfSSL_GENERAL_NAME_free(gn); + return NULL; + } + if (wolfSSL_sk_GENERAL_NAME_push(gns, gn) != 1) { + wolfSSL_GENERAL_NAME_free(gn); + wolfSSL_sk_GENERAL_NAME_pop_free(gns, wolfSSL_GENERAL_NAME_free); + return NULL; + } + return gns; +} + +/* Return the first SAN DNS string of @x509 in @out (len in @outLen), or set + * *out to NULL when no SAN is present. Returns the SAN entry count. */ +static int test_san_first_dns(WOLFSSL_X509* x509, const char** out, int* outLen) +{ + WOLFSSL_GENERAL_NAMES* sk = NULL; + WOLFSSL_GENERAL_NAME* gn = NULL; + int num = 0; + + *out = NULL; + *outLen = 0; + sk = (WOLFSSL_GENERAL_NAMES*)wolfSSL_X509_get_ext_d2i(x509, + NID_subject_alt_name, NULL, NULL); + if (sk == NULL) { + return 0; + } + num = wolfSSL_sk_GENERAL_NAME_num(sk); + gn = wolfSSL_sk_GENERAL_NAME_value(sk, 0); + if ((gn != NULL) && (gn->type == GEN_DNS) && (gn->d.dNSName != NULL)) { + *out = (const char*)gn->d.dNSName->data; + *outLen = gn->d.dNSName->length; + } + /* Note: returned pointer is owned by sk; only used for comparison before + * the stack is freed by the caller path below. */ + wolfSSL_sk_GENERAL_NAME_pop_free(sk, wolfSSL_GENERAL_NAME_free); + return num; +} +#endif + +/* Exercise the X509V3_ADD_* operation flags of wolfSSL_X509_add1_ext_i2d() + * using the subjectAltName extension. */ +static int test_wolfSSL_X509_add1_ext_i2d_flags(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(OPENSSL_ALL) && !defined(NO_CERTS) && \ + !defined(NO_ASN) + WOLFSSL_X509* x509 = NULL; + WOLFSSL_GENERAL_NAMES* gns = NULL; + int num = 0; + + ExpectNotNull(x509 = wolfSSL_X509_new()); + + /* DELETE / REPLACE_EXISTING on an empty cert must fail. */ + ExpectNotNull(gns = test_san_dns_stack("a.example")); + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_DELETE), WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_REPLACE_EXISTING), WOLFSSL_FAILURE); + + /* Unknown operation must fail. */ + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_OP_MASK), WOLFSSL_FAILURE); + + /* DEFAULT adds when absent. */ + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_DEFAULT), WOLFSSL_SUCCESS); + wolfSSL_sk_GENERAL_NAME_pop_free(gns, wolfSSL_GENERAL_NAME_free); + gns = NULL; + + /* DEFAULT on an existing extension fails. */ + ExpectNotNull(gns = test_san_dns_stack("b.example")); + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_DEFAULT), WOLFSSL_FAILURE); + /* DEFAULT|SILENT suppresses the error and appends. */ + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_DEFAULT | X509V3_ADD_SILENT), WOLFSSL_SUCCESS); + { + const char* dnsName = NULL; + int dnsLen = 0; + num = test_san_first_dns(x509, &dnsName, &dnsLen); + ExpectIntEQ(num, 2); /* appended: a.example + b.example */ + } + wolfSSL_sk_GENERAL_NAME_pop_free(gns, wolfSSL_GENERAL_NAME_free); + gns = NULL; + + /* KEEP_EXISTING is a no-op when an extension is already present. */ + ExpectNotNull(gns = test_san_dns_stack("c.example")); + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_KEEP_EXISTING), WOLFSSL_SUCCESS); + { + const char* dnsName = NULL; + int dnsLen = 0; + num = test_san_first_dns(x509, &dnsName, &dnsLen); + ExpectIntEQ(num, 2); /* unchanged */ + } + wolfSSL_sk_GENERAL_NAME_pop_free(gns, wolfSSL_GENERAL_NAME_free); + gns = NULL; + + /* REPLACE clears the existing extension and adds the new value. */ + ExpectNotNull(gns = test_san_dns_stack("d.example")); + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_REPLACE), WOLFSSL_SUCCESS); + { + const char* dnsName = NULL; + int dnsLen = 0; + num = test_san_first_dns(x509, &dnsName, &dnsLen); + ExpectIntEQ(num, 1); + ExpectNotNull(dnsName); + if (dnsName != NULL) { + ExpectIntEQ(dnsLen, (int)XSTRLEN("d.example")); + ExpectIntEQ(XMEMCMP(dnsName, "d.example", XSTRLEN("d.example")), 0); + } + } + wolfSSL_sk_GENERAL_NAME_pop_free(gns, wolfSSL_GENERAL_NAME_free); + gns = NULL; + + /* REPLACE_EXISTING now succeeds because the extension is present. */ + ExpectNotNull(gns = test_san_dns_stack("e.example")); + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_REPLACE_EXISTING), WOLFSSL_SUCCESS); + wolfSSL_sk_GENERAL_NAME_pop_free(gns, wolfSSL_GENERAL_NAME_free); + gns = NULL; + + /* DELETE removes the extension; afterward get_ext_d2i finds nothing. */ + ExpectNotNull(gns = test_san_dns_stack("f.example")); + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_subject_alt_name, gns, 0, + X509V3_ADD_DELETE), WOLFSSL_SUCCESS); + { + const char* dnsName = NULL; + int dnsLen = 0; + num = test_san_first_dns(x509, &dnsName, &dnsLen); + ExpectIntEQ(num, 0); + } + wolfSSL_sk_GENERAL_NAME_pop_free(gns, wolfSSL_GENERAL_NAME_free); + gns = NULL; + + wolfSSL_X509_free(x509); +#endif + return EXPECT_RESULT(); +} + +/* basicConstraints must round trip through wolfSSL_X509_add1_ext_i2d() and be + * consumed by wolfSSL_X509_add_ext() (previously rejected as type 0). */ +static int test_wolfSSL_X509_add1_ext_i2d_basic_constraints(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(OPENSSL_ALL) && !defined(NO_CERTS) && \ + !defined(NO_ASN) + WOLFSSL_X509* x509 = NULL; + WOLFSSL_BASIC_CONSTRAINTS* bc = NULL; + WOLFSSL_BASIC_CONSTRAINTS* rb = NULL; + + ExpectNotNull(x509 = wolfSSL_X509_new()); + ExpectNotNull(bc = wolfSSL_BASIC_CONSTRAINTS_new()); + if (bc != NULL) { + bc->ca = 1; + ExpectNotNull(bc->pathlen = wolfSSL_ASN1_INTEGER_new()); + ExpectIntEQ(wolfSSL_ASN1_INTEGER_set(bc->pathlen, 3), 1); + } + + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_basic_constraints, bc, 1, + X509V3_ADD_DEFAULT), WOLFSSL_SUCCESS); + + /* Verify it was actually applied to the in-memory cert. */ + ExpectIntEQ(wolfSSL_X509_get_isCA(x509), 1); + + ExpectNotNull(rb = (WOLFSSL_BASIC_CONSTRAINTS*)wolfSSL_X509_get_ext_d2i( + x509, NID_basic_constraints, NULL, NULL)); + if (rb != NULL) { + ExpectIntEQ(rb->ca, 1); + wolfSSL_BASIC_CONSTRAINTS_free(rb); + } + + wolfSSL_BASIC_CONSTRAINTS_free(bc); + wolfSSL_X509_free(x509); +#endif + return EXPECT_RESULT(); +} + +/* extKeyUsage is represented as an object stack that wolfSSL_X509_add_ext() + * cannot map back to the keyUsage bitmask; the wrapper must report failure + * rather than a silent success that adds nothing. */ +static int test_wolfSSL_X509_add1_ext_i2d_eku_unsupported(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(OPENSSL_ALL) && !defined(NO_CERTS) && \ + !defined(NO_ASN) + WOLFSSL_X509* x509 = NULL; + WOLFSSL_STACK* sk = NULL; + WOLFSSL_ASN1_OBJECT* obj = NULL; + + ExpectNotNull(x509 = wolfSSL_X509_new()); + ExpectNotNull(sk = wolfSSL_sk_new_asn1_obj()); + ExpectNotNull(obj = wolfSSL_OBJ_nid2obj(NID_anyExtendedKeyUsage)); + ExpectIntEQ(wolfSSL_sk_ASN1_OBJECT_push(sk, obj), 1); + if (EXPECT_FAIL()) { + wolfSSL_ASN1_OBJECT_free(obj); + } + + ExpectIntEQ(wolfSSL_X509_add1_ext_i2d(x509, NID_ext_key_usage, sk, 0, + X509V3_ADD_DEFAULT), WOLFSSL_FAILURE); + + wolfSSL_sk_ASN1_OBJECT_pop_free(sk, NULL); + wolfSSL_X509_free(x509); +#endif + return EXPECT_RESULT(); +} + /* Round trip test for wolfSSL_X509_set_authority_key_id() with a raw key ID. * * Builds a cert, calls the setter with a 20-byte raw keyId, signs the cert, @@ -34860,6 +35102,9 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_X509_SEP), TEST_DECL(test_wolfSSL_X509_set_extensions), TEST_DECL(test_wolfSSL_X509_add1_ext_i2d), + TEST_DECL(test_wolfSSL_X509_add1_ext_i2d_flags), + TEST_DECL(test_wolfSSL_X509_add1_ext_i2d_basic_constraints), + TEST_DECL(test_wolfSSL_X509_add1_ext_i2d_eku_unsupported), TEST_DECL(test_wolfSSL_X509_set_authority_key_id_roundtrip), TEST_DECL(test_wolfSSL_X509_set_authority_key_id_ex_roundtrip), TEST_DECL(test_wolfSSL_X509_set_authority_key_id_overwrite), diff --git a/wolfssl/openssl/x509v3.h b/wolfssl/openssl/x509v3.h index 480242c325b..5e039c3e358 100644 --- a/wolfssl/openssl/x509v3.h +++ b/wolfssl/openssl/x509v3.h @@ -49,6 +49,18 @@ #define WOLFSSL_XKU_DVCS 0x80 #define WOLFSSL_XKU_ANYEKU 0x100 +/* X509V3_ADD_* operation flags for wolfSSL_X509_add1_ext_i2d(). The low nibble + * selects the operation; X509V3_ADD_SILENT suppresses the "already present" + * error for X509V3_ADD_DEFAULT. */ +#define WOLFSSL_X509V3_ADD_OP_MASK 0xfL +#define WOLFSSL_X509V3_ADD_DEFAULT 0L +#define WOLFSSL_X509V3_ADD_APPEND 1L +#define WOLFSSL_X509V3_ADD_REPLACE 2L +#define WOLFSSL_X509V3_ADD_REPLACE_EXISTING 3L +#define WOLFSSL_X509V3_ADD_KEEP_EXISTING 4L +#define WOLFSSL_X509V3_ADD_DELETE 5L +#define WOLFSSL_X509V3_ADD_SILENT 0x10L + #define WOLFSSL_X509_PURPOSE_SSL_CLIENT 0 #define WOLFSSL_X509_PURPOSE_SSL_SERVER 1 @@ -182,6 +194,15 @@ WOLFSSL_API WOLFSSL_ASN1_STRING* wolfSSL_a2i_IPADDRESS(const char* ipa); #define XKU_DVCS WOLFSSL_XKU_DVCS #define XKU_ANYEKU WOLFSSL_XKU_ANYEKU +#define X509V3_ADD_OP_MASK WOLFSSL_X509V3_ADD_OP_MASK +#define X509V3_ADD_DEFAULT WOLFSSL_X509V3_ADD_DEFAULT +#define X509V3_ADD_APPEND WOLFSSL_X509V3_ADD_APPEND +#define X509V3_ADD_REPLACE WOLFSSL_X509V3_ADD_REPLACE +#define X509V3_ADD_REPLACE_EXISTING WOLFSSL_X509V3_ADD_REPLACE_EXISTING +#define X509V3_ADD_KEEP_EXISTING WOLFSSL_X509V3_ADD_KEEP_EXISTING +#define X509V3_ADD_DELETE WOLFSSL_X509V3_ADD_DELETE +#define X509V3_ADD_SILENT WOLFSSL_X509V3_ADD_SILENT + #define X509_PURPOSE_SSL_CLIENT WOLFSSL_X509_PURPOSE_SSL_CLIENT #define X509_PURPOSE_SSL_SERVER WOLFSSL_X509_PURPOSE_SSL_SERVER