diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b9e85a99bc..36bbdfb3ed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true OPENHITLS_REPO: https://gitcode.com/openHiTLS/openhitls.git # Pinned until the next openHiTLS release - OPENHITLS_REF: 0a7d5a3748f94c594832a16a7a970cb19d8b4b12 + OPENHITLS_REF: 2d524d8e0f9c392a4bc3c244f976a69f9f6bb64a jobs: pre-commit: diff --git a/examples/tls_backend_testcases.sh b/examples/tls_backend_testcases.sh index 8dfb9fe7de..f3fd502570 100755 --- a/examples/tls_backend_testcases.sh +++ b/examples/tls_backend_testcases.sh @@ -29,6 +29,8 @@ # - PKI invalid root CA directory negative path # - concurrent PSK and PKI server configuration # - PKI missing client certificate negative path +# - PKI SAN preferred over CN +# - PKI CN fallback without SAN # - PKI SNI certificate selection # - wrong PKI CA negative path # @@ -322,6 +324,8 @@ generate_pki_files () { ca_conf=$pki_dir/ca.cnf server_conf=$pki_dir/server.cnf alt_server_conf=$pki_dir/alt_server.cnf + san_server_conf=$pki_dir/san_server.cnf + cn_server_conf=$pki_dir/cn_server.cnf sni_server_conf=$pki_dir/sni_server.cnf client_conf=$pki_dir/client.cnf inter_conf=$pki_dir/inter.cnf @@ -331,6 +335,10 @@ generate_pki_files () { if [ -f "$pki_dir/server.pem" ] && [ -f "$pki_dir/self_server.pem" ] && [ -f "$pki_dir/alt_server.pem" ] && + [ -f "$pki_dir/san_server.pem" ] && + [ -f "$pki_dir/san_server.key" ] && + [ -f "$pki_dir/cn_server.pem" ] && + [ -f "$pki_dir/cn_server.key" ] && [ -f "$pki_dir/sni_combined.pem" ] && [ -f "$pki_dir/chain_server.pem" ] && [ -f "$pki_dir/chain_depth_ok_server.pem" ] && @@ -392,6 +400,40 @@ subjectAltName = @alt_names [alt_names] DNS.1 = default.invalid +EOF + + cat > "$san_server_conf" < "$cn_server_conf" < "$sni_server_conf" <> "$LOGDIR/$case_name.openssl" 2>&1 || return 1 + openssl req -new -newkey rsa:2048 -nodes -sha256 \ + -keyout "$pki_dir/san_server.key" -out "$pki_dir/san_server.csr" \ + -config "$san_server_conf" >> "$LOGDIR/$case_name.openssl" 2>&1 || return 1 + openssl x509 -req -in "$pki_dir/san_server.csr" \ + -CA "$pki_dir/ca.pem" -CAkey "$pki_dir/ca.key" -CAcreateserial \ + -out "$pki_dir/san_server.pem" -days 1 -sha256 \ + -extensions v3_req -extfile "$san_server_conf" \ + >> "$LOGDIR/$case_name.openssl" 2>&1 || return 1 + openssl req -new -newkey rsa:2048 -nodes -sha256 \ + -keyout "$pki_dir/cn_server.key" -out "$pki_dir/cn_server.csr" \ + -config "$cn_server_conf" >> "$LOGDIR/$case_name.openssl" 2>&1 || return 1 + openssl x509 -req -in "$pki_dir/cn_server.csr" \ + -CA "$pki_dir/ca.pem" -CAkey "$pki_dir/ca.key" -CAcreateserial \ + -out "$pki_dir/cn_server.pem" -days 1 -sha256 \ + -extensions v3_req -extfile "$cn_server_conf" \ + >> "$LOGDIR/$case_name.openssl" 2>&1 || return 1 openssl req -new -newkey rsa:2048 -nodes -sha256 \ -keyout "$pki_dir/sni_server.key" -out "$pki_dir/sni_server.csr" \ -config "$sni_server_conf" >> "$LOGDIR/$case_name.openssl" 2>&1 || return 1 @@ -1471,6 +1529,60 @@ run_pki_missing_client_cert () { fi } +run_pki_san_preferred_over_cn () { + case_name=pki_san_preferred_over_cn + echo -n "PKI SAN preferred over CN - " + pki_dir=$LOGDIR/pki + + if ! generate_pki_files "$case_name"; then + fail_case "$case_name" "certificate generation failed" + return + fi + if ! start_pki_server "$case_name" "$pki_dir/san_server.pem" \ + "$pki_dir/san_server.key"; then + fail_case "$case_name" "server did not start" + return + fi + + run_pki_client "$case_name" "$pki_dir/ca.pem" "$CLIENT_TIMEOUT" + + if assert_contains "$LOGDIR/$case_name.client" "COAP_EVENT_DTLS_CONNECTED" && + assert_contains "$LOGDIR/$case_name.client" "2\\.05" && + assert_contains "$LOGDIR/$case_name.client" "CN '$SNI_HOST' presented by server" && + assert_contains "$LOGDIR/$case_name.server" "call handler for pseudo resource '.well-known/core'"; then + pass_case + else + fail_case "$case_name" "PKI SAN did not override mismatching CN" + fi +} + +run_pki_cn_fallback () { + case_name=pki_cn_fallback + echo -n "PKI CN fallback without SAN - " + pki_dir=$LOGDIR/pki + + if ! generate_pki_files "$case_name"; then + fail_case "$case_name" "certificate generation failed" + return + fi + if ! start_pki_server "$case_name" "$pki_dir/cn_server.pem" \ + "$pki_dir/cn_server.key"; then + fail_case "$case_name" "server did not start" + return + fi + + run_pki_client "$case_name" "$pki_dir/ca.pem" "$CLIENT_TIMEOUT" + + if assert_contains "$LOGDIR/$case_name.client" "COAP_EVENT_DTLS_CONNECTED" && + assert_contains "$LOGDIR/$case_name.client" "2\\.05" && + assert_contains "$LOGDIR/$case_name.client" "CN '$SNI_HOST' presented by server" && + assert_contains "$LOGDIR/$case_name.server" "call handler for pseudo resource '.well-known/core'"; then + pass_case + else + fail_case "$case_name" "PKI CN fallback without SAN did not complete" + fi +} + run_pki_sni () { case_name=pki_sni echo -n "PKI SNI certificate selection - " @@ -1557,6 +1669,8 @@ run_pki_root_ca_file_invalid run_pki_root_ca_dir_invalid run_psk_pki_dual_mode run_pki_missing_client_cert +run_pki_san_preferred_over_cn +run_pki_cn_fallback run_pki_sni run_wrong_pki_ca diff --git a/src/coap_openhitls.c b/src/coap_openhitls.c index 1a28ea6703..c553fb0073 100644 --- a/src/coap_openhitls.c +++ b/src/coap_openhitls.c @@ -49,6 +49,7 @@ #include #include #include +#include #include #if defined(__GNUC__) #pragma GCC diagnostic pop @@ -787,6 +788,76 @@ coap_hitls_verify_cb_self_signed_allowed(const coap_dtls_pki_t *setup_data, depth == 0 && coap_hitls_cert_is_self_signed(cert); } +static char * +coap_hitls_copy_name(const uint8_t *data, uint32_t data_len) { + char *copy; + size_t len = (size_t)data_len; + + if (data_len && !data) + return NULL; + copy = (char *)coap_malloc_type(COAP_STRING, len + 1); + if (!copy) + return NULL; + if (len) + memcpy(copy, data, len); + copy[len] = '\000'; + return copy; +} + +static char * +coap_hitls_get_san_from_cert(HITLS_X509_Cert *cert) { + HITLS_X509_ExtSan san = {0}; + char *dns_name = NULL; + + if (HITLS_X509_CertCtrl(cert, HITLS_X509_EXT_GET_SAN, &san, + sizeof(san)) == HITLS_PKI_SUCCESS && + san.names) { + for (BslListNode *name_node = BSL_LIST_FirstNode(san.names); + name_node != NULL; + name_node = BSL_LIST_GetNextNode(san.names, name_node)) { + const HITLS_X509_GeneralName *name = + (const HITLS_X509_GeneralName *)BSL_LIST_GetData(name_node); + + if (!name || name->type != HITLS_X509_GN_DNS) + continue; + if (name->value.dataLen && + memchr(name->value.data, '\000', name->value.dataLen)) + continue; + dns_name = coap_hitls_copy_name(name->value.data, name->value.dataLen); + break; + } + } + + HITLS_X509_ClearSubjectAltName(&san); + return dns_name; +} + +static char * +coap_hitls_get_cn_from_cert(HITLS_X509_Cert *cert) { + BSL_Buffer cn = {0}; + char *cn_name = NULL; + + if (HITLS_X509_CertCtrl(cert, HITLS_X509_GET_SUBJECT_CN_STR, + &cn, sizeof(cn)) == HITLS_PKI_SUCCESS) { + cn_name = coap_hitls_copy_name(cn.data, cn.dataLen); + } + if (cn.data) + BSL_SAL_Free(cn.data); + return cn_name; +} + +static char * +coap_hitls_get_san_or_cn_from_cert(HITLS_X509_Cert *cert) { + char *name; + + if (!cert) + return NULL; + name = coap_hitls_get_san_from_cert(cert); + if (name) + return name; + return coap_hitls_get_cn_from_cert(cert); +} + static int coap_hitls_validate_cn_cert(coap_session_t *session, const coap_dtls_pki_t *setup_data, @@ -795,32 +866,30 @@ coap_hitls_validate_cn_cert(coap_session_t *session, int validated) { uint8_t *der = NULL; uint32_t der_len = 0; - BSL_Buffer cn; + char *san_or_cn; int ret = 1; - memset(&cn, 0, sizeof(cn)); (void)HITLS_X509_CertCtrl(cert, HITLS_X509_GET_ENCODELEN, &der_len, sizeof(der_len)); if (der_len) (void)HITLS_X509_CertCtrl(cert, HITLS_X509_GET_ENCODE, &der, sizeof(der)); - (void)HITLS_X509_CertCtrl(cert, HITLS_X509_GET_SUBJECT_CN_STR, - &cn, sizeof(cn)); + san_or_cn = coap_hitls_get_san_or_cn_from_cert(cert); if (setup_data->validate_cn_call_back) { coap_lock_callback_ret(ret, setup_data->validate_cn_call_back( - cn.data ? (const char *)cn.data : "", + san_or_cn ? san_or_cn : "", 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)) { + san_or_cn && strcmp(san_or_cn, setup_data->client_sni)) { ret = 0; } - if (cn.data) - BSL_SAL_Free(cn.data); + if (san_or_cn) + coap_free_type(COAP_STRING, san_or_cn); return ret; }