internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) { EventSourceTrace("ClientCertificateOption: {0}", clientCertOption, easy: easy); Debug.Assert(clientCertOption == ClientCertificateOption.Automatic || clientCertOption == ClientCertificateOption.Manual); // Create a client certificate provider if client certs may be used. X509Certificate2Collection clientCertificates = easy._handler._clientCertificates; ClientCertificateProvider certProvider = clientCertOption == ClientCertificateOption.Automatic ? new ClientCertificateProvider(null) : // automatic clientCertificates?.Count > 0 ? new ClientCertificateProvider(clientCertificates) : // manual with certs null; // manual without certs IntPtr userPointer = IntPtr.Zero; if (certProvider != null) { EventSourceTrace("Created certificate provider", easy: easy); // The client cert provider needs to be passed through to the callback, and thus // we create a GCHandle to keep it rooted. This handle needs to be cleaned up // when the request has completed, and a simple and pay-for-play way to do that // is by cleaning it up in a continuation off of the request. userPointer = GCHandle.ToIntPtr(certProvider._gcHandle); easy.Task.ContinueWith((_, state) => ((IDisposable)state).Dispose(), certProvider, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } // Configure the options. Our best support is when targeting OpenSSL/1.0. For other backends, // we fall back to a minimal amount of support, and may throw a PNSE based on the options requested. if (CurlSslVersionDescription.IndexOf(Interop.Http.OpenSsl10Description, StringComparison.OrdinalIgnoreCase) != -1) { // Register the callback with libcurl. We need to register even if there's no user-provided // server callback and even if there are no client certificates, because we support verifying // server certificates against more than those known to OpenSSL. SetSslOptionsForSupportedBackend(easy, certProvider, userPointer); } else { // Newer versions of OpenSSL, and other non-OpenSSL backends, do not currently support callbacks. // That means we'll throw a PNSE if a callback is required. SetSslOptionsForUnsupportedBackend(easy, certProvider); } }
internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) { Debug.Assert( clientCertOption == ClientCertificateOption.Automatic || clientCertOption == ClientCertificateOption.Manual); // Create a client certificate provider if client certs may be used. X509Certificate2Collection clientCertificates = easy._handler._clientCertificates; if (clientCertOption != ClientCertificateOption.Manual || clientCertificates?.Count > 0) { // libcurl does not have an option of accepting a SecIdentityRef via an input option, // only via writing it to a file and letting it load the PFX. // This would require that a) we write said file, and b) that it contaminate the default // keychain (because their PFX loader loads to the default keychain). throw new PlatformNotSupportedException(SR.net_http_libcurl_clientcerts_notsupported_os); } // Revocation checking is always on for darwinssl (SecureTransport). // If any other backend is used and revocation is requested, we can't guarantee // that assertion. if (easy._handler.CheckCertificateRevocationList && !CurlSslVersionDescription.Equals(Interop.Http.SecureTransportDescription)) { throw new PlatformNotSupportedException( SR.Format( SR.net_http_libcurl_revocation_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.SecureTransportDescription)); } if (easy._handler.ServerCertificateCustomValidationCallback != null) { // libcurl (as of 7.49.1) does not have any callback which can be registered which fires // between the time that a TLS/SSL handshake has offered up the server certificate and the // time that the HTTP request headers are written. Were there any callback, the option // CURLINFO_TLS_SSL_PTR could be queried (and the backend identifier validated to be // CURLSSLBACKEND_DARWINSSL). Then the SecTrustRef could be extracted to build the chain, // a la SslStream. // // Without the callback the matrix looks like: // * If default-trusted and callback-would-trust: No difference (except side effects, like logging). // * If default-trusted and callback-would-block: Data would have been sent in violation of user trust. // * If not-default-trusted and callback-would-not-trust: No difference (except side effects). // * If not-default-trusted and callback-would-trust: No data sent, which doesn't match user desires. // // Of the two "different" cases, sending when we shouldn't is worse, so that's the direction we // have to cater to. So we'll use default trust, and throw on any custom callback. // // The situation where system trust fails can be remedied by including the certificate into the // user's keychain and setting the SSL policy trust for it to "Always Trust". // Similarly, the "block this" could be attained by setting the SSL policy for a cert in the // keychain to "Never Trust". // // However, one case we can support is when we know all certificates will pass validation. // We can detect a key case of that: whether DangerousAcceptAnyServerCertificateValidator was used. if (easy.ServerCertificateValidationCallbackAcceptsAll) { EventSourceTrace("Warning: Disabling peer verification per {0}", nameof(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator), easy: easy); easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYPEER, 0); // don't verify the peer // Don't set CURLOPT_SSL_VERIFHOST to 0; doing so disables SNI with SecureTransport backend. if (!CurlSslVersionDescription.Equals(Interop.Http.SecureTransportDescription)) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0); // don't verify the hostname } } else { throw new PlatformNotSupportedException(SR.net_http_libcurl_callback_notsupported_os); } } SetSslVersion(easy); }
internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) { Debug.Assert(clientCertOption == ClientCertificateOption.Automatic || clientCertOption == ClientCertificateOption.Manual); // Create a client certificate provider if client certs may be used. X509Certificate2Collection clientCertificates = easy._handler._clientCertificates; ClientCertificateProvider certProvider = clientCertOption == ClientCertificateOption.Automatic ? new ClientCertificateProvider(null) : // automatic clientCertificates?.Count > 0 ? new ClientCertificateProvider(clientCertificates) : // manual with certs null; // manual without certs IntPtr userPointer = IntPtr.Zero; if (certProvider != null) { // The client cert provider needs to be passed through to the callback, and thus // we create a GCHandle to keep it rooted. This handle needs to be cleaned up // when the request has completed, and a simple and pay-for-play way to do that // is by cleaning it up in a continuation off of the request. userPointer = GCHandle.ToIntPtr(certProvider._gcHandle); easy.Task.ContinueWith((_, state) => ((IDisposable)state).Dispose(), certProvider, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } // Register the callback with libcurl. We need to register even if there's no user-provided // server callback and even if there are no client certificates, because we support verifying // server certificates against more than those known to OpenSSL. if (CurlSslVersionDescription.IndexOf("openssl/1.0", StringComparison.OrdinalIgnoreCase) != -1) { CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback, userPointer); switch (answer) { case CURLcode.CURLE_OK: // We successfully registered. If we'll be invoking a user-provided callback to verify the server // certificate as part of that, disable libcurl's verification of the host name. The user's callback // needs to be given the opportunity to examine the cert, and our logic will determine whether // the host name matches and will inform the callback of that. if (easy._handler.ServerCertificateValidationCallback != null) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0); // don't verify the peer cert's hostname // We don't change the SSL_VERIFYPEER setting, as setting it to 0 will cause // SSL and libcurl to ignore the result of the server callback. } // The allowed SSL protocols will be set in the configuration callback. break; case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.38 and prior case CURLcode.CURLE_NOT_BUILT_IN: // Curl 7.39 and later // It's ok if we failed to register the callback if all of the defaults are in play // with relation to handling of certificates. But if that's not the case, failing to // register the callback will result in those options not being factored in, which is // a significant enough error that we need to fail. EventSourceTrace("CURLOPT_SSL_CTX_FUNCTION not supported: {0}", answer, easy: easy); if (certProvider != null || easy._handler.ServerCertificateValidationCallback != null || easy._handler.CheckCertificateRevocationList) { throw new PlatformNotSupportedException( SR.Format(SR.net_http_unix_invalid_certcallback_option, CurlVersionDescription, CurlSslVersionDescription)); } // Since there won't be a callback to configure the allowed SSL protocols, configure them here. SetSslVersion(easy); break; default: ThrowIfCURLEError(answer); break; } } else { // For newer versions of openssl throw PNSE, if default not used. if (certProvider != null) { throw new PlatformNotSupportedException( SR.Format( SR.net_http_libcurl_clientcerts_notsupported, CurlVersionDescription, CurlSslVersionDescription)); } if (easy._handler.ServerCertificateValidationCallback != null) { throw new PlatformNotSupportedException( SR.Format( SR.net_http_libcurl_callback_notsupported, CurlVersionDescription, CurlSslVersionDescription)); } if (easy._handler.CheckCertificateRevocationList) { throw new PlatformNotSupportedException( SR.Format( SR.net_http_libcurl_revocation_notsupported, CurlVersionDescription, CurlSslVersionDescription)); } // In case of defaults configure the allowed SSL protocols. SetSslVersion(easy); } }