diff --git a/CMakeLists.txt b/CMakeLists.txt index de09a2fb2..503f12fc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1724,21 +1724,23 @@ if(WOLFSSL_SYS_CA_CERTS) override_cache(WOLFSSL_SYS_CA_CERTS "no") elseif(APPLE) check_include_file("Security/SecTrustSettings.h" HAVE_SECURITY_SECTRUSTSETTINGS_H) - if(NOT HAVE_SECURITY_SECTRUSTSETTINGS_H) - message("Can't enable system CA certs without Security/SecTrustSettings.h.") - override_cache(WOLFSSL_SYS_CA_CERTS "no") - else() + check_include_file("Security/SecCertificate.h" HAVE_SECURITY_SECCERTIFICATE_H) + check_include_file("Security/SecTrust.h" HAVE_SECURITY_SECTRUST_H) + check_include_file("Security/SecPolicy.h" HAVE_SECURITY_SECPOLICY_H) + if(HAVE_SECURITY_SECTRUSTSETTINGS_H OR (HAVE_SECURITY_SECCERTIFICATE_H + AND HAVE_SECURITY_SECTRUST_H + AND HAVE_SECURITY_SECPOLICY_H)) find_library(CORE_FOUNDATION_FRAMEWORK CoreFoundation) if(NOT CORE_FOUNDATION_FRAMEWORK) - message("Can't enable system CA certs without CoreFoundation framework.") - override_cache(WOLFSSL_SYS_CA_CERTS "no") + message(FATAL_ERROR "Can't enable system CA certs without CoreFoundation framework.") else() find_library(SECURITY_FRAMEWORK Security) if(NOT SECURITY_FRAMEWORK) - message("Can't enable system CA certs without Security framework.") - override_cache(WOLFSSL_SYS_CA_CERTS "no") + message(FATAL_ERROR "Can't enable system CA certs without Security framework.") endif() endif() + else() + message(FATAL_ERROR "Can't enable system CA certs without Apple Security.framework headers.") endif() endif() diff --git a/configure.ac b/configure.ac index 031d0e349..70deb2428 100644 --- a/configure.ac +++ b/configure.ac @@ -8351,14 +8351,24 @@ then case $host_os in *darwin*) - AC_CHECK_HEADERS([Security/SecTrustSettings.h], + # Creates the HAVE_SECURITY_SECXXX_H macros in config.h + AC_CHECK_HEADERS([Security/SecTrustSettings.h]) + AC_CHECK_HEADERS([Security/SecCertificate.h]) + AC_CHECK_HEADERS([Security/SecTrust.h]) + AC_CHECK_HEADERS([Security/SecPolicy.h]) + # Either Security/SecTrustSettings (for MacOS cert loading), or the + # trio of Security/SecCertificate.h, Security/SecTrust.h, and + # Security/SecPolicy.h (for native trust APIs other apple devices) + # must be present + AS_IF([test -n "$ac_cv_header_Security_SecTrustSettings_h" \ + || (test -n "$ac_cv_header_Security_SecCertificate_h" \ + && test -n "$ac_cv_header_Security_SecTrust_h" \ + && test -n "$ac_cv_header_Security_SecPolicy_h")], [ - # For Mac we need these frameworks to load system CA certs LDFLAGS="$LDFLAGS -framework CoreFoundation -framework Security" ], [ - AC_MSG_NOTICE([Can't enable system CA certs without Security/SecTrustSettings.h]) - ENABLED_SYS_CA_CERTS="no" + AC_MSG_ERROR([Unable to find Apple Security.framework headers]) ] ) ;; diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 8cb8f6eb1..6fe45a084 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -1236,7 +1236,9 @@ int wolfSSL_CTX_load_verify_locations_ex(WOLFSSL_CTX* ctx, const char* file, \brief This function returns a pointer to an array of strings representing directories wolfSSL will search for system CA certs when - wolfSSL_CTX_load_system_CA_certs is called. + wolfSSL_CTX_load_system_CA_certs is called. On systems that don't store + certificates in an accessible system directory (such as Apple platforms), + this function will always return NULL. \return Valid pointer on success. \return NULL pointer on failure. @@ -1266,10 +1268,19 @@ const char** wolfSSL_get_system_CA_dirs(word32* num); /*! \ingroup CertsKeys - \brief This function attempts to load CA certificates into a WOLFSSL_CTX - from an OS-dependent CA certificate store. Loaded certificates will be - trusted. The platforms supported and tested are: Linux (Debian, Ubuntu, - Gentoo, Fedora, RHEL), Windows 10/11, Android, Apple OS X and iOS. + \brief On most platforms (including Linux and Windows), this function + attempts to load CA certificates into a WOLFSSL_CTX from an OS-dependent + CA certificate store. Loaded certificates will be trusted. + + On Apple platforms (excluding macOS), certificates can't be obtained from + the system, and therefore cannot be loaded into the wolfSSL certificate + manager. For these platforms, this function enables TLS connections bound to + the WOLFSSL_CTX to use the native system trust APIs to verify authenticity + of the peer certificate chain if the authenticity of the peer cannot first + be authenticated against certificates loaded by the user. + + The platforms supported and tested are: Linux (Debian, Ubuntu, + Gentoo, Fedora, RHEL), Windows 10/11, Android, macOS, and iOS. \return WOLFSSL_SUCCESS on success. \return WOLFSSL_BAD_PATH if no system CA certs were loaded. diff --git a/src/internal.c b/src/internal.c index 60c5f0e7a..6b4468fb0 100644 --- a/src/internal.c +++ b/src/internal.c @@ -217,6 +217,14 @@ WOLFSSL_CALLBACKS needs LARGE_STATIC_BUFFERS, please add LARGE_STATIC_BUFFERS static int _DtlsCheckWindow(WOLFSSL* ssl); #endif +#if defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) +#include +#include +#include +static int DoAppleNativeCertValidation(const WOLFSSL_BUFFER_INFO* certs, + int totalCerts); +#endif /* #if defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) */ + #ifdef WOLFSSL_DTLS13 #ifndef WOLFSSL_DTLS13_SEND_MOREACK_DEFAULT #define WOLFSSL_DTLS13_SEND_MOREACK_DEFAULT 0 @@ -271,6 +279,7 @@ static int SSL_hmac(WOLFSSL* ssl, byte* digest, const byte* in, word32 sz, const unsigned char* secret, int secretSz, void* ctx); #endif + /* Label string for client random. */ #define SSC_CR "CLIENT_RANDOM" @@ -2426,6 +2435,11 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) maxq10xx_SetupPkCallbacks(ctx, &method->version); #endif /* WOLFSSL_MAXQ10XX_TLS */ +#if defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) + /* Should only be set when wolfSSL_CTX_load_system_CA_certs() is called */ + ctx->doAppleNativeCertValidationFlag = 0; +#endif /* defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) */ + return ret; } @@ -14205,6 +14219,24 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif /* WOLFSSL_ALT_CERT_CHAINS */ + #if defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) + /* If we are using native Apple CA validation, it is okay + * for a CA cert to fail validation here, as we will verify + * the entire chain when we hit the peer (leaf) cert */ + if (ssl->ctx->doAppleNativeCertValidationFlag) { + WOLFSSL_MSG("Bypassing errors to allow for Apple native" + " CA validation"); + ret = 0; /* clear errors and continue */ + args->verifyErr = 0; + #if defined(OPENSSL_EXTRA) \ + || defined(OPENSSL_EXTRA_X509_SMALL) + ssl->peerVerifyRet = 0; + #endif + /* do not add to certificate manager */ + skipAddCA = 1; + } + #endif /* defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) */ + /* Do verify callback */ ret = DoVerifyCallback(SSL_CM(ssl), ssl, ret, args); if (ssl->options.verifyNone && @@ -14273,7 +14305,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, FreeDecodedCert(args->dCert); args->dCertInit = 0; args->count--; - } /* while (count > 0 && !args->haveTrustPeer) */ + } /* while (count > 1 && !args->haveTrustPeer) */ } /* if (count > 0) */ /* Check for error */ @@ -14394,6 +14426,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } else { WOLFSSL_MSG("Failed to verify Peer's cert"); + #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) if (ssl->peerVerifyRet == 0) { /* Return first cert error here */ if (ret == ASN_BEFORE_DATE_E) { @@ -14411,6 +14444,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } } #endif + if (ssl->verifyCallback) { WOLFSSL_MSG( "\tCallback override available, will continue"); @@ -14419,6 +14453,18 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, if (args->fatal) DoCertFatalAlert(ssl, ret); } + #if defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) + /* Disregard failure to verify peer cert, as we will verify + * the whole chain with the native API later */ + else if (ssl->ctx->doAppleNativeCertValidationFlag) { + WOLFSSL_MSG("\tApple native CA validation override" + " available, will continue"); + /* check if fatal error */ + args->fatal = (args->verifyErr) ? 1 : 0; + if (args->fatal) + DoCertFatalAlert(ssl, ret); + } + #endif/*defined(__APPLE__)&& defined(WOLFSSL_SYS_CA_CERTS)*/ else { WOLFSSL_MSG("\tNo callback override available, fatal"); args->fatal = 1; @@ -15178,6 +15224,22 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif + #if defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) + /* If we can't validate the peer cert chain against the CAs loaded + * into wolfSSL, try to validate against the system certificates + * using Apple's native trust APIs */ + if ((ret != 0) && (ssl->ctx->doAppleNativeCertValidationFlag)) { + if (DoAppleNativeCertValidation(args->certs, + args->totalCerts)) { + WOLFSSL_MSG("Apple native cert chain validation SUCCESS"); + ret = 0; + } + else { + WOLFSSL_MSG("Apple native cert chain validation FAIL"); + } + } + #endif /* defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) */ + /* Do verify callback */ ret = DoVerifyCallback(SSL_CM(ssl), ssl, ret, args); @@ -39353,6 +39415,139 @@ int wolfSSL_sk_BY_DIR_entry_push(WOLF_STACK_OF(WOLFSSL_BY_DIR_entry)* sk, #endif /* OPENSSL_ALL */ +#if defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) + +/* + * Converts a DER formatted certificate to a SecCertificateRef + * + * @param derCert pointer to the DER formatted certificate + * @param derLen length of the DER formatted cert, in bytes + * + * @return The newly created SecCertificateRef. Must be freed by caller when + * no longer in use + */ +static SecCertificateRef ConvertToSecCertificateRef(const byte* derCert, + int derLen) +{ + CFDataRef derData = NULL; + SecCertificateRef secCert = NULL; + + WOLFSSL_ENTER("ConvertToSecCertificateRef"); + + /* Create a CFDataRef from the DER encoded certificate */ + derData = CFDataCreate(kCFAllocatorDefault, derCert, derLen); + if (!derData) { + WOLFSSL_MSG("Error: can't create CFDataRef object for DER cert"); + goto cleanup; + } + + /* Create a SecCertificateRef from the CFDataRef */ + secCert = SecCertificateCreateWithData(kCFAllocatorDefault, derData); + if (!secCert) { + WOLFSSL_MSG("Error: can't create SecCertificateRef from CFDataRef"); + goto cleanup; + } + +cleanup: + if (derData) { + CFRelease(derData); + } + + WOLFSSL_LEAVE("ConvertToSecCertificateRef", !!secCert); + + return secCert; +} + + +/* + * Validates a chain of certificates using the Apple system trust APIs + * + * @param certs pointer to the certificate chain to validate + * @param totalCerts the number of certificates in certs + * + * @return 1 if chain is valid and trusted + * @return 0 if chain is invalid or untrusted + * + * As of MacOS 14.0 we are still able to access system certificates and load + * them manually into wolfSSL. For other apple devices, apple has removed the + * ability to obtain certificates from the trust store, so we can't use + * wolfSSL's built-in certificate validation mechanisms anymore. We instead + * must call into the Security Framework APIs to authenticate peer certificates + */ +static int DoAppleNativeCertValidation(const WOLFSSL_BUFFER_INFO* certs, + int totalCerts) +{ + int i; + int ret; + OSStatus status; + CFMutableArrayRef certArray = NULL; + SecCertificateRef secCert = NULL; + SecTrustRef trust = NULL; + SecPolicyRef policy = NULL ; + + WOLFSSL_ENTER("DoAppleNativeCertValidation"); + + certArray = CFArrayCreateMutable(kCFAllocatorDefault, + totalCerts, + &kCFTypeArrayCallBacks); + if (!certArray) { + WOLFSSL_MSG("Error: can't allocate CFArray for certificates"); + ret = 0; + goto cleanup; + } + + for (i = 0; i < totalCerts; i++) { + secCert = ConvertToSecCertificateRef(certs[i].buffer, certs[i].length); + if (!secCert) { + WOLFSSL_MSG("Error: can't convert DER cert to SecCertificateRef"); + ret = 0; + goto cleanup; + } + else { + CFArrayAppendValue(certArray, secCert); + /* Release, since the array now holds the reference */ + CFRelease(secCert); + } + } + + /* Create trust object for SecCertifiate Ref */ + policy = SecPolicyCreateSSL(true, NULL); + status = SecTrustCreateWithCertificates(certArray, policy, &trust); + if (status != errSecSuccess) { + WOLFSSL_MSG_EX("Error creating trust object, " + "SecTrustCreateWithCertificates returned %d",status); + ret = 0; + goto cleanup; + } + + /* Evaluate the certificate's authenticity */ + if (SecTrustEvaluateWithError(trust, NULL) == 1) { + WOLFSSL_MSG("Cert chain is trusted"); + ret = 1; + } + else { + WOLFSSL_MSG("Cert chain trust evaluation failed" + "SecTrustEvaluateWithError returned 0"); + ret = 0; + } + + /* Cleanup */ +cleanup: + if (certArray) { + CFRelease(certArray); + } + if (trust) { + CFRelease(trust); + } + if (policy) { + CFRelease(policy); + } + + WOLFSSL_LEAVE("DoAppleNativeCertValidation", ret); + + return ret; +} +#endif /* defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) */ #undef ERROR_OUT diff --git a/src/ssl.c b/src/ssl.c index b9fbe40cf..8a8fef5f9 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -8403,6 +8403,18 @@ static int LoadSystemCaCertsWindows(WOLFSSL_CTX* ctx, byte* loaded) #elif defined(__APPLE__) +#if defined(HAVE_SECURITY_SECTRUSTSETTINGS_H) \ + && !defined(WOLFSSL_APPLE_NATIVE_CERT_VALIDATION) +/* + * Manually obtains certificates from the system trust store and loads them + * directly into wolfSSL "the old way". + * + * As of MacOS 14.0 we are still able to use this method to access system + * certificates. Accessiblity of this API is indicated by the presence of the + * Security/SecTrustSettings.h header. In the likely event that Apple removes + * access to this API on Macs, this function should be removed and the + * DoAppleNativeCertValidation() routine should be used for all devices. + */ static int LoadSystemCaCertsMac(WOLFSSL_CTX* ctx, byte* loaded) { int ret = WOLFSSL_SUCCESS; @@ -8463,6 +8475,7 @@ static int LoadSystemCaCertsMac(WOLFSSL_CTX* ctx, byte* loaded) return ret; } +#endif /* defined(HAVE_SECURITY_SECTRUSTSETTINGS_H) */ #else @@ -8536,11 +8549,48 @@ int wolfSSL_CTX_load_system_CA_certs(WOLFSSL_CTX* ctx) WOLFSSL_ENTER("wolfSSL_CTX_load_system_CA_certs"); #ifdef USE_WINDOWS_API + ret = LoadSystemCaCertsWindows(ctx, &loaded); + #elif defined(__APPLE__) + +#if defined(HAVE_SECURITY_SECTRUSTSETTINGS_H) \ + && !defined(WOLFSSL_APPLE_NATIVE_CERT_VALIDATION) + /* As of MacOS 14.0 we are still able to access system certificates and + * load them manually into wolfSSL "the old way". Accessiblity of this API + * is indicated by the presence of the Security/SecTrustSettings.h header */ ret = LoadSystemCaCertsMac(ctx, &loaded); +#elif defined(WOLFSSL_APPLE_NATIVE_CERT_VALIDATION) \ + || (defined(HAVE_SECURITY_SECCERTIFICATE_H) \ + && defined(HAVE_SECURITY_SECTRUST_H) \ + && defined(HAVE_SECURITY_SECPOLICY_H)) + /* For other Apple devices, Apple has removed the ability to obtain + * certificates from the trust store, so we can't use wolfSSL's built-in + * certificate validation mechanisms anymore. We instead must call into the + * Security Framework APIs to authenticate peer certificates when received. + * (see src/internal.c:DoAppleNativeCertValidation()). + * Thus, there is no CA "loading" required, but to keep behavior consistent + * with the current API (not using system CA certs unless this function has + * been called), we simply set a flag indicating that the new apple trust + * verification routine should be used later */ + ctx->doAppleNativeCertValidationFlag = 1; + ret = WOLFSSL_SUCCESS; + loaded = 1; #else +/* HAVE_SECURITY_SECXXX_H macros are set by autotools or CMake when searching + * system for the required SDK headers. If building with user_settings.h, you + * will need to manually define WOLFSSL_APPLE_NATIVE_CERT_VALIDATION + * and ensure the appropriate Security.framework headers and libraries are + * visible to your compiler */ +#error "WOLFSSL_SYS_CA_CERTS on Apple devices requires Security.framework" \ + " header files to be detected, or a manual override with" \ + " WOLFSSL_APPLE_NATIVE_CERT_VALIDATION" +#endif + +#else + ret = LoadSystemCaCertsNix(ctx, &loaded); + #endif if (ret == WOLFSSL_SUCCESS && !loaded) { diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 10c395551..cdb33c5d8 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3922,6 +3922,9 @@ struct WOLFSSL_CTX { #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) WOLFSSL_EchConfig* echConfigs; #endif +#if defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) + byte doAppleNativeCertValidationFlag:1; +#endif /* defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) */ }; WOLFSSL_LOCAL diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 3bbb6e89b..5da62be86 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -3143,11 +3143,6 @@ extern void uITRON4_free(void *p) ; /* Turning off WOLFSSL_SYS_CA_CERTS b/c NO_CERTS is defined */ #undef WOLFSSL_SYS_CA_CERTS #endif - - #if defined(__APPLE__) && !defined(HAVE_SECURITY_SECTRUSTSETTINGS_H) - /* Turning off WOLFSSL_SYS_CA_CERTS b/c no Security/SecTrustSettings.h header */ - #undef WOLFSSL_SYS_CA_CERTS - #endif #endif /* WOLFSSL_SYS_CA_CERTS */ #if defined(SESSION_CACHE_DYNAMIC_MEM) && defined(PERSIST_SESSION_CACHE)