diff --git a/src/internal.c b/src/internal.c index 6c5a70ecc9c..7d702b920bb 100644 --- a/src/internal.c +++ b/src/internal.c @@ -26975,6 +26975,12 @@ static const char* wolfSSL_ERR_reason_error_string_OpenSSL(unsigned long e) case WOLFSSL_X509_V_ERR_SUBJECT_ISSUER_MISMATCH: return "subject issuer mismatch"; + case WOLFSSL_X509_V_ERR_HOSTNAME_MISMATCH: + return "hostname mismatch"; + + case WOLFSSL_X509_V_ERR_IP_ADDRESS_MISMATCH: + return "IP address mismatch"; + default: return NULL; } diff --git a/src/x509_str.c b/src/x509_str.c index b2249016244..b246002787e 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -740,6 +740,33 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) wolfSSL_sk_X509_free(certsToUse); } + /* Enforce hostname / IP verification from X509_VERIFY_PARAM if set. + * Always check against the leaf (end-entity) certificate, captured in + * orig before the chain-building loop modified ctx->current_cert. */ + if (ret == WOLFSSL_SUCCESS && ctx->param != NULL) { + if (ctx->param->hostName[0] != '\0') { + if (wolfSSL_X509_check_host(orig, + ctx->param->hostName, + XSTRLEN(ctx->param->hostName), + ctx->param->hostFlags, NULL) != WOLFSSL_SUCCESS) { + ctx->error = WOLFSSL_X509_V_ERR_HOSTNAME_MISMATCH; + ctx->error_depth = 0; + ctx->current_cert = orig; + ret = WOLFSSL_FAILURE; + } + } + else if (ctx->param->ipasc[0] != '\0') { + if (wolfSSL_X509_check_ip_asc(orig, + ctx->param->ipasc, + ctx->param->hostFlags) != WOLFSSL_SUCCESS) { + ctx->error = WOLFSSL_X509_V_ERR_IP_ADDRESS_MISMATCH; + ctx->error_depth = 0; + ctx->current_cert = orig; + ret = WOLFSSL_FAILURE; + } + } + } + return ret == WOLFSSL_SUCCESS ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; } diff --git a/tests/api.c b/tests/api.c index 9e9689b9e8c..cf68def205a 100644 --- a/tests/api.c +++ b/tests/api.c @@ -27057,6 +27057,8 @@ static int error_test(void) {17, 15}, {19, 19}, {27, 26 }, + {61, 30}, + {63, 63}, #endif { -9, WC_SPAN1_FIRST_E + 1 }, { -124, -124 }, diff --git a/tests/api/test_x509.c b/tests/api/test_x509.c index 5b63281d29b..4ab8d6545b3 100644 --- a/tests/api/test_x509.c +++ b/tests/api/test_x509.c @@ -244,6 +244,102 @@ int test_x509_GetCAByAKID(void) return EXPECT_RESULT(); } +/* Regression test: wolfSSL_X509_verify_cert() must honour the hostname set via + * X509_VERIFY_PARAM_set1_host(). Before the fix the hostname was stored in + * ctx->param->hostName but never consulted, so any chain-valid certificate + * would pass regardless of hostname mismatch (RFC 6125 sec. 6.4.1 violation). + * + * Uses existing PEM fixtures: + * svrCertFile - CN=www.wolfssl.com, SAN DNS=example.com, SAN IP=127.0.0.1 + * caCertFile - CA that signed svrCertFile + */ +int test_x509_verify_cert_hostname_check(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) + WOLFSSL_X509_STORE* store = NULL; + WOLFSSL_X509_STORE_CTX* ctx = NULL; + WOLFSSL_X509* ca = NULL; + WOLFSSL_X509* leaf = NULL; + WOLFSSL_X509_VERIFY_PARAM* param = NULL; + + ExpectNotNull(store = wolfSSL_X509_STORE_new()); + ExpectNotNull(ca = wolfSSL_X509_load_certificate_file(caCertFile, + SSL_FILETYPE_PEM)); + ExpectIntEQ(wolfSSL_X509_STORE_add_cert(store, ca), WOLFSSL_SUCCESS); + + ExpectNotNull(leaf = wolfSSL_X509_load_certificate_file(svrCertFile, + SSL_FILETYPE_PEM)); + + /* Case 1: no hostname constraint - must succeed. */ + ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new()); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS); + wolfSSL_X509_STORE_CTX_free(ctx); + ctx = NULL; + + /* Case 2: hostname matches a SAN DNS entry - must succeed. */ + ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new()); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL), + WOLFSSL_SUCCESS); + param = wolfSSL_X509_STORE_CTX_get0_param(ctx); + ExpectNotNull(param); + ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_host(param, "example.com", + XSTRLEN("example.com")), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS); + wolfSSL_X509_STORE_CTX_free(ctx); + ctx = NULL; + + /* Case 3: hostname does not match - must FAIL with the right error code. */ + ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new()); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL), + WOLFSSL_SUCCESS); + param = wolfSSL_X509_STORE_CTX_get0_param(ctx); + ExpectNotNull(param); + ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_host(param, "wrong.com", + XSTRLEN("wrong.com")), WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_get_error(ctx), + X509_V_ERR_HOSTNAME_MISMATCH); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_get_error_depth(ctx), 0); + wolfSSL_X509_STORE_CTX_free(ctx); + ctx = NULL; + + /* Case 4: IP matches a SAN IP entry - must succeed. */ + ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new()); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL), + WOLFSSL_SUCCESS); + param = wolfSSL_X509_STORE_CTX_get0_param(ctx); + ExpectNotNull(param); + ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_ip_asc(param, "127.0.0.1"), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS); + wolfSSL_X509_STORE_CTX_free(ctx); + ctx = NULL; + + /* Case 5: IP does not match - must FAIL with the right error code. */ + ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new()); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL), + WOLFSSL_SUCCESS); + param = wolfSSL_X509_STORE_CTX_get0_param(ctx); + ExpectNotNull(param); + ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_ip_asc(param, "192.168.1.1"), + WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_get_error(ctx), + X509_V_ERR_IP_ADDRESS_MISMATCH); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_get_error_depth(ctx), 0); + wolfSSL_X509_STORE_CTX_free(ctx); + ctx = NULL; + + wolfSSL_X509_free(leaf); + wolfSSL_X509_free(ca); + wolfSSL_X509_STORE_free(store); +#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_RSA */ + return EXPECT_RESULT(); +} + int test_x509_set_serialNumber(void) { #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) diff --git a/tests/api/test_x509.h b/tests/api/test_x509.h index d0edaa2ad5a..b43f74722b1 100644 --- a/tests/api/test_x509.h +++ b/tests/api/test_x509.h @@ -25,10 +25,12 @@ int test_x509_rfc2818_verification_callback(void); int test_x509_GetCAByAKID(void); int test_x509_set_serialNumber(void); +int test_x509_verify_cert_hostname_check(void); #define TEST_X509_DECLS \ TEST_DECL_GROUP("x509", test_x509_rfc2818_verification_callback), \ TEST_DECL_GROUP("x509", test_x509_GetCAByAKID), \ - TEST_DECL_GROUP("x509", test_x509_set_serialNumber) + TEST_DECL_GROUP("x509", test_x509_set_serialNumber), \ + TEST_DECL_GROUP("x509", test_x509_verify_cert_hostname_check) #endif /* WOLFCRYPT_TEST_X509_H */ diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index e5df24edf63..dee01e860fd 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2685,7 +2685,9 @@ enum { WOLFSSL_X509_V_ERR_PATH_LENGTH_EXCEEDED = 25, WOLFSSL_X509_V_ERR_CERT_REJECTED = 28, WOLFSSL_X509_V_ERR_SUBJECT_ISSUER_MISMATCH = 29, - WC_OSSL_V509_V_ERR_MAX = 30, + WOLFSSL_X509_V_ERR_HOSTNAME_MISMATCH = 62, + WOLFSSL_X509_V_ERR_IP_ADDRESS_MISMATCH = 64, + WC_OSSL_V509_V_ERR_MAX = 65, #ifdef HAVE_OCSP /* OCSP Flags */