diff --git a/CMakeLists.txt b/CMakeLists.txt index 2713fa41..a41613b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,7 @@ set(CMAKE_MACOSX_RPATH TRUE) # micro version is changed with a set of small changes or bugfixes anywhere in the project. set(LIBNETCONF2_MAJOR_VERSION 4) set(LIBNETCONF2_MINOR_VERSION 6) -set(LIBNETCONF2_MICRO_VERSION 1) +set(LIBNETCONF2_MICRO_VERSION 2) set(LIBNETCONF2_VERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION}.${LIBNETCONF2_MICRO_VERSION}) # Version of the library diff --git a/doc/libnetconf.doc b/doc/libnetconf.doc index 3abe81c0..de9353d8 100644 --- a/doc/libnetconf.doc +++ b/doc/libnetconf.doc @@ -467,8 +467,9 @@ * * You may also choose to use a Certificate Revocation List. These lists * are downloaded from the URIs specified in the x509 CRLDistributionPoints extensions. - * Be mindful that if any CRL is successfully downloaded and set, then at least one of them has to belong - * to the peer (e.g. the client) certificate (in other words it has to be issued by peer's CA). + * Every certificate in the peer's chain that a CRL was obtained for is then checked for revocation. + * Be mindful that a certificate issued by a CA which publishes no CRL, or whose CRL could not be + * downloaded, is accepted with a warning, because its revocation status can not be determined. * * Functions List * -------------- diff --git a/src/session_mbedtls.c b/src/session_mbedtls.c index ddde452a..a7fa5fe7 100644 --- a/src/session_mbedtls.c +++ b/src/session_mbedtls.c @@ -1258,6 +1258,8 @@ nc_tls_verify_cert_chain_crl_wrap(void *cert_chain, void *cert_store, void *crl_ return 0; } + /* CAs with no CRL in the store are silently left unchecked by MbedTLS, which is the + * behavior the OpenSSL backend emulates with its verification callback */ ret = mbedtls_x509_crt_verify((mbedtls_x509_crt *)peer_chain, (mbedtls_x509_crt *)trust_ca, (mbedtls_x509_crl *)ca_crl, NULL, &flags, NULL, NULL); diff --git a/src/session_openssl.c b/src/session_openssl.c index 9afaf0af..e54bb3c0 100644 --- a/src/session_openssl.c +++ b/src/session_openssl.c @@ -831,6 +831,47 @@ nc_tls_get_peer_cert_chain_wrap(void *tls_session) return SSL_get0_verified_chain(tls_session); } +/** + * @brief Certificate chain verification callback tolerating missing CRLs. + * + * CRL checking is enabled for the whole chain, but not every CA in a chain publishes a CRL + * (offline root CAs typically do not and intermediate certificates often have no CRL distribution + * point at all). Treat a certificate with no CRL available as unchecked instead of failing the + * whole verification, so that revocation is still enforced for every certificate a CRL was + * obtained for. + * + * @param[in] ok Whether the current verification step succeeded. + * @param[in,out] ctx Certificate store context of the verification. + * @return 1 to continue the verification, 0 to fail it. + */ +static int +nc_tls_verify_crl_cb(int ok, X509_STORE_CTX *ctx) +{ + char *subject; + int depth; + + if (ok || (X509_STORE_CTX_get_error(ctx) != X509_V_ERR_UNABLE_TO_GET_CRL)) { + return ok; + } + + subject = nc_server_tls_get_subject_wrap(X509_STORE_CTX_get_current_cert(ctx)); + depth = X509_STORE_CTX_get_error_depth(ctx); + if (depth) { + /* publishing a CRL is optional for a CA, so this is an expected static property of the deployed PKI */ + VRB(NULL, "No CRL available for CA certificate \"%s\" (depth %d).", subject ? subject : "", depth); + } else { + /* either we failed to download it or there was another cert in the chain that had a CRL dist point, + * either way, not knowing the revocation status is a high risk for the peer certificate, so we log a warning */ + WRN(NULL, "No CRL available for the peer certificate \"%s\", its revocation status is unknown.", + subject ? subject : ""); + } + free(subject); + + /* clear the error so that it is not reported once the verification finishes */ + X509_STORE_CTX_set_error(ctx, X509_V_OK); + return 1; +} + int nc_tls_verify_cert_chain_crl_wrap(void *cert_chain, void *cert_store, void *UNUSED(crl_store)) { @@ -892,8 +933,10 @@ nc_tls_verify_cert_chain_crl_wrap(void *cert_chain, void *cert_store, void *UNUS untrusted = NULL; } - /* enable CRL checks for all certificates in the chain */ + /* enable CRL checks for all the certificates in the chain, certificates without an available + * CRL are tolerated by the callback */ X509_STORE_CTX_set_flags(verify_ctx, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + X509_STORE_CTX_set_verify_cb(verify_ctx, nc_tls_verify_crl_cb); ret = X509_verify_cert(verify_ctx); if (ret != 1) { diff --git a/src/session_wrapper.h b/src/session_wrapper.h index c4f1032a..d9ac1418 100644 --- a/src/session_wrapper.h +++ b/src/session_wrapper.h @@ -780,13 +780,17 @@ void *nc_tls_get_peer_cert_chain_wrap(void *tls_session); /** * @brief Verify a certificate chain against CRLs. * - * For OpenSSL, uses X509_STORE_CTX with X509_V_FLAG_CRL_CHECK | - * X509_V_FLAG_CRL_CHECK_ALL to verify the chain including CRL signature checks. + * Every certificate in the chain that a CRL was obtained for is checked, including its + * CRL signature. Certificates with no CRL available are left unchecked, since publishing + * a CRL is optional for a CA. + * + * For OpenSSL, uses X509_STORE_CTX with X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL + * and a verification callback tolerating the certificates with no CRL available. * cert_store must contain both CAs and CRLs. crl_store is unused. * * For MbedTLS, uses mbedtls_x509_crt_verify with the CRL store, which verifies - * CRL signatures as part of chain verification. Both cert_store (CA certs) - * and crl_store (CRLs) are required. + * CRL signatures as part of chain verification and skips the CAs with no CRL on its own. + * Both cert_store (CA certs) and crl_store (CRLs) are required. * * @param[in] cert_chain Peer's certificate chain. * @param[in] cert_store Certificate store (OpenSSL: contains CAs + CRLs, MbedTLS: CA certs).