diff --git a/examples/coap-client.c b/examples/coap-client.c index 3aec823743..02f249c17b 100644 --- a/examples/coap-client.c +++ b/examples/coap-client.c @@ -1648,17 +1648,18 @@ setup_pki(coap_context_t *ctx, coap_dtls_role_t role) { * but this is used to define what checking actually takes place. */ dtls_pki.verify_peer_cert = verify_peer_cert; - dtls_pki.check_common_ca = !root_ca_file; - dtls_pki.allow_self_signed = 1; + dtls_pki.check_common_ca = ca_file != NULL; + dtls_pki.allow_self_signed = !(root_ca_file || ca_file);; dtls_pki.allow_expired_certs = 1; dtls_pki.cert_chain_validation = 1; dtls_pki.cert_chain_verify_depth = 2; dtls_pki.check_cert_revocation = 1; dtls_pki.allow_no_crl = 1; dtls_pki.allow_expired_crl = 1; - dtls_pki.is_rpk_not_cert = is_rpk_not_cert; - dtls_pki.use_cid = setup_cid; - dtls_pki.validate_cn_call_back = verify_cn_callback; + dtls_pki.is_rpk_not_cert = is_rpk_not_cert; + dtls_pki.use_cid = setup_cid; + dtls_pki.allow_sni_cn_mismatch = 0; /* Not recommended to set */ + dtls_pki.validate_cn_call_back = verify_cn_callback; if (role == COAP_DTLS_ROLE_CLIENT) { if (proxy.host.length) { snprintf(client_sni, sizeof(client_sni), "%*.*s", (int)proxy.host.length, (int)proxy.host.length, diff --git a/examples/tls_backend_testcases.sh b/examples/tls_backend_testcases.sh index e80bae94f3..8dfb9fe7de 100755 --- a/examples/tls_backend_testcases.sh +++ b/examples/tls_backend_testcases.sh @@ -661,7 +661,7 @@ run_pki_client () { if [ -n "$root_ca_file" ]; then client_args="$client_args -R $root_ca_file" - else + elif [ -n "$ca_file" ]; then client_args="$client_args -C $ca_file" fi @@ -786,7 +786,8 @@ run_psk_identity_hint_callback () { run_client "$case_name" "$PSK_KEY" "" "$CLIENT_TIMEOUT" "$TARGET_URI" \ "$hint_file" - if assert_contains "$LOGDIR/$case_name.client" "does not support Identity Hint selection"; then + if assert_contains "$LOGDIR/$case_name.client" "does not support Identity Hint" || + assert_contains "$LOGDIR/$case_name.server" "does not support Identity Hint" ; then skip_case "Identity Hint not supported" elif assert_contains "$LOGDIR/$case_name.client" "Identity Hint '$PSK_HINT' provided" && assert_contains "$LOGDIR/$case_name.client" "Switching to using '$PSK_IDENTITY' identity \\+ '$PSK_KEY' key" && @@ -1167,7 +1168,7 @@ run_pki_chain_depth_too_long () { assert_not_contains "$LOGDIR/$case_name.client" "2\\.05" && assert_not_contains "$LOGDIR/$case_name.server" "call handler for pseudo resource '.well-known/core'" && assert_either_contains "$LOGDIR/$case_name.client" "$LOGDIR/$case_name.server" \ - "certificate chain too long|constraint limits|unknown_ca|0x400000f|alert|No response received"; then + "The certificate's verify depth is too long|certificate chain too long|constraint limits|unknown_ca|0x400000f|alert|No response received"; then pass_case else fail_case "$case_name" "PKI chain beyond configured depth limit was accepted" @@ -1189,7 +1190,7 @@ run_pki_self_signed_leaf () { return fi - run_pki_client "$case_name" "" "$CLIENT_TIMEOUT" no "$pki_dir/bad_ca.pem" + run_pki_client "$case_name" "" "$CLIENT_TIMEOUT" if assert_contains "$LOGDIR/$case_name.client" "COAP_EVENT_DTLS_CONNECTED" && assert_contains "$LOGDIR/$case_name.client" "2\\.05" && @@ -1241,7 +1242,7 @@ run_pki_non_self_signed_override () { return fi - run_pki_client "$case_name" "" "$CLIENT_TIMEOUT" no "$pki_dir/bad_ca.pem" + run_pki_client "$case_name" "" "$CLIENT_TIMEOUT" if assert_not_contains "$LOGDIR/$case_name.client" "COAP_EVENT_DTLS_CONNECTED" && assert_not_contains "$LOGDIR/$case_name.server" "call handler for pseudo resource '.well-known/core'" && diff --git a/include/coap3/coap_dtls.h b/include/coap3/coap_dtls.h index 1e62678f2a..8f6bc08d5d 100644 --- a/include/coap3/coap_dtls.h +++ b/include/coap3/coap_dtls.h @@ -338,14 +338,17 @@ struct coap_dtls_pki_t { uint8_t use_cid; /**< 1 if DTLS Connection ID is to be * used (Client only, server always enabled) * if supported */ - uint8_t reserved[2]; /**< Reserved - must be set to 0 for + uint8_t allow_sni_cn_mismatch:1; /**< 1 if SNI and returnd CN allowed to mismatch + * (Client only). Not normally set */ + uint8_t reserved[1]; /**< Reserved - must be set to 0 for future compatibility */ - /* Size of 2 chosen to align to next + /* Size of 1 chosen to align to next * parameter, so if newly defined option * it can use one of the reserved slots so * no need to change * COAP_DTLS_PKI_SETUP_VERSION and just * decrement the reserved[] count. + * [Now just adding 1 bit at a time]. */ /** CN check callback function. diff --git a/man/coap-client.txt.in b/man/coap-client.txt.in index 0036e718cf..8b32731454 100644 --- a/man/coap-client.txt.in +++ b/man/coap-client.txt.in @@ -298,12 +298,13 @@ definitions have to be in DER, not PEM, format. Otherwise all of Disable remote peer certificate checking. *-C* cafile:: -PEM file or PKCS11 URI for the CA certificate and any intermediate CAs that was + PEM file or PKCS11 URI for the CA certificate and any intermediate CAs that was used to sign the server certfile. Ideally the client certificate should be signed by the same CA so that mutual authentication can take place. The contents of cafile are added to the trusted store of root CAs. Using the *-C* or *-R* options will trigger - the validation of the server certificate unless overridden by the *-n* option. + the validation of the server certificate (including not allowing self-signed + certificates) unless overridden by the *-n* option. *-J* pkcs11_pin:: The user pin to unlock access to the PKCS11 token. @@ -320,8 +321,8 @@ PEM file or PKCS11 URI for the CA certificate and any intermediate CAs that was to be in this list and is trusted for the validation. Using *-R trust_casfile* disables common CA mutual authentication which can only be done by using *-C cafile*. Using the *-C* or *-R* options will - trigger the validation of the server certificate unless overridden by the - *-n* option. + trigger the validation of the server certificate (including not allowing + self-signed certificates) unless overridden by the *-n* option. *-Y* :: Do not load the default system Trusted Root CA Store. diff --git a/man/coap_encryption.txt.in b/man/coap_encryption.txt.in index efc49e824a..4d1a067665 100644 --- a/man/coap_encryption.txt.in +++ b/man/coap_encryption.txt.in @@ -515,6 +515,8 @@ typedef struct coap_dtls_pki_t { uint8_t use_cid; /* 1 if DTLS Connection ID is to be * used (Client only, server always enabled) * if supported */ + uint8_t allow_sni_cn_mismatch:1; /* 1 if SNI and returnd CN allowed to mismatch + * (Client only). Not normally set */ uint8_t reserved[2]; /* Reserved - must be set to 0 for future compatibility */ @@ -574,8 +576,8 @@ for different versions of the coap_dtls_pki_t structure in the future. *verify_peer_cert* Set to 1 to check that the peer's certificate is valid if provided, else 0. If not set, check_common_ca, allow_self_signed, allow_expired_certs, cert_chain_validation, cert_chain_verify_depth, -check_cert_revocation, allow_no_crl, allow_expired_crl, allow_bad_md_hash -and allow_short_rsa_length settings are all ignored. +check_cert_revocation, allow_no_crl, allow_expired_crl, allow_bad_md_hash, +allow_short_rsa_length and allow_sni_cn_mismatch settings are all ignored. *check_common_ca* Set to 1 to check that the CA that signed the peer's certificate is the same CA that signed the local certificate @@ -624,7 +626,11 @@ allow_no_crl, allow_expired_crl, allow_bad_md_hash and allow_short_rsa_length settings are all ignored. *use_cid* Set to 1 if the DTLS Client is to try to used Connection-ID. -Server is alwys enabled if support available. +Server is always enabled if support available. + +*allow_sni_cn_mismatch* Set to 1 if the DTLS Client is not to compare the +Common Name (CN) of the received certificate with the sent SNI hostname. +This is not recommended. *SECTION: PKI/RPK: coap_dtls_pki_t: Reserved* @@ -1168,6 +1174,8 @@ setup_server_context_pki(const char *public_cert_file, dtls_pki.allow_bad_md_hash = 0; dtls_pki.allow_short_rsa_length = 0; dtls_pki.is_rpk_not_cert = 0; /* Set to 1 if RPK */ + dtls_pki.use_cid = 0; + dtls_pki.allow_sni_cn_mismatch = 0; dtls_pki.validate_cn_call_back = verify_cn_callback; dtls_pki.cn_call_back_arg = valid_cn_list; dtls_pki.validate_sni_call_back = verify_pki_sni_callback; diff --git a/man/coap_endpoint_client.txt.in b/man/coap_endpoint_client.txt.in index 949fe7d050..710691d0aa 100644 --- a/man/coap_endpoint_client.txt.in +++ b/man/coap_endpoint_client.txt.in @@ -425,6 +425,8 @@ setup_client_session_pki(const char *host, dtls_pki.allow_bad_md_hash = 0; dtls_pki.allow_short_rsa_length = 0; dtls_pki.is_rpk_not_cert = 0; /* Set to 1 if RPK */ + dtls_pki.use_cid = 0; + dtls_pki.allow_sni_cn_mismatch = 0; dtls_pki.validate_cn_call_back = verify_cn_callback; dtls_pki.cn_call_back_arg = NULL; dtls_pki.validate_sni_call_back = NULL; diff --git a/man/coap_endpoint_server.txt.in b/man/coap_endpoint_server.txt.in index f9356d9b71..17da3b7f0b 100644 --- a/man/coap_endpoint_server.txt.in +++ b/man/coap_endpoint_server.txt.in @@ -390,6 +390,8 @@ setup_server_context_pki(const char *public_cert_file, dtls_pki.allow_bad_md_hash = 0; dtls_pki.allow_short_rsa_length = 0; dtls_pki.is_rpk_not_cert = 0; /* Set to 1 if RPK */ + dtls_pki.use_cid = 0; + dtls_pki.allow_sni_cn_mismatch = 0; dtls_pki.validate_cn_call_back = verify_cn_callback; dtls_pki.cn_call_back_arg = valid_cn_list; dtls_pki.validate_sni_call_back = verify_pki_sni_callback; diff --git a/man/coap_tls_library.txt.in b/man/coap_tls_library.txt.in index d4e2ae8f3e..3ed40e40a3 100644 --- a/man/coap_tls_library.txt.in +++ b/man/coap_tls_library.txt.in @@ -211,6 +211,9 @@ setup_server_context_pki(const char *public_cert_file, dtls_pki.check_cert_revocation = 1; dtls_pki.allow_no_crl = 1; dtls_pki.allow_expired_crl = 1; + dtls_pki.is_rpk_not_cert = 0; /* Set to 1 if RPK */ + dtls_pki.use_cid = 0; + dtls_pki.allow_sni_cn_mismatch = 0; dtls_pki.pki_key.key_type = COAP_PKI_KEY_DEFINE; dtls_pki.pki_key.key.define.ca.s_byte = ca_file; dtls_pki.pki_key.key.define.public_cert.s_byte = public_cert_file; diff --git a/src/coap_gnutls.c b/src/coap_gnutls.c index beca2b9c8c..91019391e3 100644 --- a/src/coap_gnutls.c +++ b/src/coap_gnutls.c @@ -867,8 +867,18 @@ cert_verify_gnutls(gnutls_session_t g_session) { goto fail; } - G_CHECK(gnutls_certificate_verify_peers(g_session, NULL, 0, &status), - "gnutls_certificate_verify_peers"); + if (c_session->type == COAP_SESSION_TYPE_CLIENT && g_context->setup_data.client_sni) { + gnutls_typed_vdata_st host; + + host.type = GNUTLS_DT_DNS_HOSTNAME; + host.data = (uint8_t *)g_context->setup_data.client_sni; + host.size = strlen(g_context->setup_data.client_sni); + G_CHECK(gnutls_certificate_verify_peers(g_session, &host, 1, &status), + "gnutls_certificate_verify_peers"); + } else { + G_CHECK(gnutls_certificate_verify_peers(g_session, NULL, 0, &status), + "gnutls_certificate_verify_peers"); + } coap_dtls_log(COAP_LOG_DEBUG, "error %x cert '%s'\n", status, cert_info.san_or_cn); @@ -922,6 +932,23 @@ cert_verify_gnutls(gnutls_session_t g_session) { OUTPUT_CERT_NAME); } } + if (status & (GNUTLS_CERT_UNEXPECTED_OWNER)) { + status &= ~(GNUTLS_CERT_UNEXPECTED_OWNER); + if (g_context->setup_data.allow_sni_cn_mismatch) { + coap_log_info(" %s: %s: overridden: '%s' v '%s'\n", + coap_session_str(c_session), + "The SNI does not match the returned CN", + g_context->setup_data.client_sni, + OUTPUT_CERT_NAME); + } else { + fail = 1; + coap_log_warn(" %s: %s: '%s' v '%s'\n", + coap_session_str(c_session), + "The SNI does not match the returned CN", + g_context->setup_data.client_sni, + OUTPUT_CERT_NAME); + } + } if (status & (GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED| GNUTLS_CERT_REVOCATION_DATA_ISSUED_IN_FUTURE)) { status &= ~(GNUTLS_CERT_REVOCATION_DATA_SUPERSEDED| diff --git a/src/coap_mbedtls.c b/src/coap_mbedtls.c index 758530f22f..69f5c3de0d 100644 --- a/src/coap_mbedtls.c +++ b/src/coap_mbedtls.c @@ -679,12 +679,15 @@ cert_verify_callback_mbedtls(void *data, mbedtls_x509_crt *crt, char *cn = NULL; cn = get_san_or_cn_from_cert(crt); + coap_dtls_log(COAP_LOG_DEBUG, "depth %d flags %x cert '%s'\n", + depth, *flags, cn); + if (depth == 0 && c_session->type == COAP_SESSION_TYPE_CLIENT && setup_data->client_sni && cn && + strcmp(cn, setup_data->client_sni)) { + *flags |= MBEDTLS_X509_BADCERT_CN_MISMATCH; + } if (setup_data->validate_cn_call_back) { int ret; - if (*flags & MBEDTLS_X509_BADCERT_CN_MISMATCH) { - *flags &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; - } coap_lock_callback_ret(ret, setup_data->validate_cn_call_back(cn, crt->raw.p, @@ -697,6 +700,14 @@ cert_verify_callback_mbedtls(void *data, mbedtls_x509_crt *crt, *flags |= MBEDTLS_X509_BADCERT_CN_MISMATCH; } } + if (setup_data->cert_chain_validation && + depth > (setup_data->cert_chain_verify_depth + 2)) { + *flags |= MBEDTLS_X509_BADCERT_OTHER; + coap_log_warn(" %s: %s: '%s' depth %d\n", + coap_session_str(c_session), + "The certificate's verify depth is too long", + cn ? cn : "?", depth); + } if (*flags == 0) { if (cn) mbedtls_free(cn); @@ -735,6 +746,16 @@ cert_verify_callback_mbedtls(void *data, mbedtls_x509_crt *crt, "The certificate has a short RSA length", cn ? cn : "?", depth); } } + if (*flags & MBEDTLS_X509_BADCERT_CN_MISMATCH) { + if (setup_data->allow_sni_cn_mismatch) { + *flags &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; + coap_log_info(" %s: %s: overridden: '%s' v '%s' depth %d\n", + coap_session_str(c_session), + "The SNI does not match the returned CN", + setup_data->client_sni ? setup_data->client_sni : "", + cn ? cn : "?", depth); + } + } if (*flags & MBEDTLS_X509_BADCERT_NOT_TRUSTED) { uint32_t lflags; int self_signed = !mbedtls_x509_crt_verify(crt, crt, NULL, NULL, &lflags, @@ -785,14 +806,6 @@ cert_verify_callback_mbedtls(void *data, mbedtls_x509_crt *crt, *flags &= ~MBEDTLS_X509_BADCRL_FUTURE; } } - if (setup_data->cert_chain_validation && - depth > (setup_data->cert_chain_verify_depth + 1)) { - *flags |= MBEDTLS_X509_BADCERT_OTHER; - coap_log_warn(" %s: %s: '%s' depth %d\n", - coap_session_str(c_session), - "The certificate's verify depth is too long", - cn ? cn : "?", depth); - } if (*flags != 0) { char buf[128]; @@ -2401,6 +2414,9 @@ coap_dtls_context_set_spsk(coap_context_t *c_context, coap_log_warn("Mbed TLS not compiled for EC-JPAKE support\n"); #endif /* ! MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED */ } + if (setup_data->psk_info.hint.length) { + coap_log_warn("CoAP Server with Mbed TLS does not support Identity Hint\n"); + } m_context->psk_pki_enabled |= IS_PSK; return 1; } diff --git a/src/coap_openhitls.c b/src/coap_openhitls.c index df4466f201..1a28ea6703 100644 --- a/src/coap_openhitls.c +++ b/src/coap_openhitls.c @@ -796,10 +796,8 @@ coap_hitls_validate_cn_cert(coap_session_t *session, uint8_t *der = NULL; uint32_t der_len = 0; BSL_Buffer cn; - int ret; + int ret = 1; - if (!setup_data->validate_cn_call_back) - return 1; memset(&cn, 0, sizeof(cn)); (void)HITLS_X509_CertCtrl(cert, HITLS_X509_GET_ENCODELEN, &der_len, sizeof(der_len)); @@ -809,11 +807,18 @@ coap_hitls_validate_cn_cert(coap_session_t *session, (void)HITLS_X509_CertCtrl(cert, HITLS_X509_GET_SUBJECT_CN_STR, &cn, sizeof(cn)); - coap_lock_callback_ret(ret, - setup_data->validate_cn_call_back( - cn.data ? (const char *)cn.data : "", - der, der_len, session, depth, validated, - setup_data->cn_call_back_arg)); + if (setup_data->validate_cn_call_back) { + coap_lock_callback_ret(ret, + setup_data->validate_cn_call_back( + cn.data ? (const char *)cn.data : "", + der, der_len, session, depth, validated, + setup_data->cn_call_back_arg)); + } + if (depth == 0 && session->type == COAP_SESSION_TYPE_CLIENT && + setup_data->client_sni && !setup_data->allow_sni_cn_mismatch && + cn.data && strcmp((const char *)cn.data, setup_data->client_sni)) { + ret = 0; + } if (cn.data) BSL_SAL_Free(cn.data); return ret; diff --git a/src/coap_openssl.c b/src/coap_openssl.c index 4bdc098272..9564046e12 100644 --- a/src/coap_openssl.c +++ b/src/coap_openssl.c @@ -2469,7 +2469,6 @@ tls_verify_call_back(int preverify_ok, X509_STORE_CTX *ctx) { int err = X509_STORE_CTX_get_error(ctx); X509 *x509 = X509_STORE_CTX_get_current_cert(ctx); char *cn = x509 ? get_san_or_cn_from_cert(x509) : NULL; - int keep_preverify_ok = preverify_ok; coap_dtls_log(COAP_LOG_DEBUG, "depth %d error %x preverify %d cert '%s'\n", depth, err, preverify_ok, cn); @@ -2478,6 +2477,12 @@ tls_verify_call_back(int preverify_ok, X509_STORE_CTX *ctx) { OPENSSL_free(cn); return 0; } + if (depth == 0 && session->type == COAP_SESSION_TYPE_CLIENT && setup_data->client_sni && + !setup_data->allow_sni_cn_mismatch && cn && strcmp(cn, setup_data->client_sni)) { + preverify_ok = 0; + X509_STORE_CTX_set_error(ctx, X509_V_ERR_SUBJECT_ISSUER_MISMATCH); + err = X509_V_ERR_SUBJECT_ISSUER_MISMATCH; + } if (!preverify_ok) { switch (err) { case X509_V_ERR_CERT_NOT_YET_VALID: @@ -2508,6 +2513,10 @@ tls_verify_call_back(int preverify_ok, X509_STORE_CTX *ctx) { if (!setup_data->verify_peer_cert) preverify_ok = 1; break; + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + if (setup_data->allow_sni_cn_mismatch) + preverify_ok = 1; + break; default: break; } @@ -2534,7 +2543,7 @@ tls_verify_call_back(int preverify_ok, X509_STORE_CTX *ctx) { } } /* Certificate - depth == 0 is the Client Cert */ - if (setup_data->validate_cn_call_back && keep_preverify_ok) { + if (setup_data->validate_cn_call_back) { int length = i2d_X509(x509, NULL); uint8_t *base_buf; uint8_t *base_buf2 = base_buf = length > 0 ? OPENSSL_malloc(length) : NULL; diff --git a/src/coap_wolfssl.c b/src/coap_wolfssl.c index 146820c952..46aa2f9f0f 100644 --- a/src/coap_wolfssl.c +++ b/src/coap_wolfssl.c @@ -385,11 +385,11 @@ coap_dtls_rpk_is_supported(void) { */ int coap_dtls_cid_is_supported(void) { -#if defined(HAVE_RPK) && LIBWOLFSSL_VERSION_HEX >= 0x05006004 +#if defined(WOLFSSL_DTLS_CID) return 1; -#else /* ! HAVE_RPK || LIBWOLFSSL_VERSION_HEX < 0x05006004 */ +#else /* ! WOLFSSL_DTLS_CID */ return 0; -#endif /* ! HAVE_RPK || LIBWOLFSSL_VERSION_HEX < 0x05006004 */ +#endif /* ! WOLFSSL_DTLS_CID */ } #if COAP_CLIENT_SUPPORT @@ -1565,6 +1565,12 @@ tls_verify_call_back(int preverify_ok, WOLFSSL_X509_STORE_CTX *ctx) { } coap_dtls_log(COAP_LOG_DEBUG, "depth %d error %x preverify %d cert '%s'\n", depth, err, preverify_ok, cn ? cn : ""); + if (depth == 0 && session->type == COAP_SESSION_TYPE_CLIENT && setup_data->client_sni && cn && + strcmp(cn, setup_data->client_sni)) { + preverify_ok = 0; + X509_STORE_CTX_set_error(ctx, X509_V_ERR_SUBJECT_ISSUER_MISMATCH); + err = X509_V_ERR_SUBJECT_ISSUER_MISMATCH; + } if (!preverify_ok) { switch (err) { case X509_V_ERR_CERT_NOT_YET_VALID: @@ -1597,6 +1603,10 @@ tls_verify_call_back(int preverify_ok, WOLFSSL_X509_STORE_CTX *ctx) { if (!setup_data->verify_peer_cert) preverify_ok = 1; break; + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + if (setup_data->allow_sni_cn_mismatch) + preverify_ok = 1; + break; default: break; } diff --git a/tests/test_tls.c b/tests/test_tls.c index f0acedabcb..14fa70101e 100644 --- a/tests/test_tls.c +++ b/tests/test_tls.c @@ -275,6 +275,7 @@ t_tls5(void) { } #endif /* COAP_SERVER_SUPPORT */ +#if COAP_CLIENT_SUPPORT static void t_tls6(void) { coap_context_t *ctx; @@ -288,6 +289,7 @@ t_tls6(void) { coap_free_context(ctx); } +#endif /* COAP_CLIENT_SUPPORT */ static void t_tls7(void) { @@ -1188,7 +1190,9 @@ t_init_tls_tests(void) { TLS_TEST(suite, t_tls4); TLS_TEST(suite, t_tls5); #endif /* COAP_SERVER_SUPPORT */ +#if COAP_CLIENT_SUPPORT TLS_TEST(suite, t_tls6); +#endif /* COAP_CLIENT_SUPPORT */ TLS_TEST(suite, t_tls7); TLS_TEST(suite, t_tls8); #if COAP_SERVER_SUPPORT