private static void HandleRedirectLocationHeader(EasyRequest state, string locationValue) { Debug.Assert(state._isRedirect); Debug.Assert(state._handler.AutomaticRedirection); string location = locationValue.Trim(); //only for absolute redirects Uri forwardUri; if (Uri.TryCreate(location, UriKind.RelativeOrAbsolute, out forwardUri) && forwardUri.IsAbsoluteUri) { KeyValuePair <NetworkCredential, CURLAUTH> ncAndScheme = GetCredentials(state._handler.Credentials as CredentialCache, forwardUri); if (ncAndScheme.Key != null) { state.SetCredentialsOptions(ncAndScheme); } else { state.SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); } // reset proxy - it is possible that the proxy has different credentials for the new URI state.SetProxyOptions(forwardUri); if (state._handler._useCookie) { // reset cookies. state.SetCurlOption(CURLoption.CURLOPT_COOKIE, IntPtr.Zero); // set cookies again state.SetCookieOption(forwardUri); } } }
private static void SetSslOptionsForUnsupportedBackend(EasyRequest easy, ClientCertificateProvider certProvider) { if (certProvider != null) { throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_clientcerts_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.RequiredOpenSslDescription)); } if (easy._handler.CheckCertificateRevocationList) { throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_revocation_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.RequiredOpenSslDescription)); } if (easy._handler.ServerCertificateCustomValidationCallback != null) { if (easy.ServerCertificateValidationCallbackAcceptsAll) { EventSourceTrace("Warning: Disabling peer and host verification per {0}", nameof(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator), easy: easy); easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYPEER, 0); easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0); } else { throw new PlatformNotSupportedException(SR.Format(SR.net_http_libcurl_callback_notsupported_sslbackend, CurlVersionDescription, CurlSslVersionDescription, Interop.Http.RequiredOpenSslDescription)); } } else { SetSslOptionsForCertificateStore(easy); } // In case of defaults configure the allowed SSL protocols. SetSslVersion(easy); }
private void ActivateNewRequest(SafeCurlMultiHandle multiHandle, EasyRequest easy) { Debug.Assert(easy != null, "We should never get a null request"); // If cancellation has been requested, complete the request proactively if (easy.CancellationToken.IsCancellationRequested) { easy.FailRequest(new OperationCanceledException(easy.CancellationToken)); easy.Cleanup(); // no active processing remains, so cleanup return; } // Otherwise, configure it. Most of the configuration was already done when the EasyRequest // was created, but there's additional configuration we need to do specific to this // multi agent, specifically telling the easy request about its own GCHandle and setting // up callbacks for data processing. Once it's configured, add it to the multi handle. GCHandle gcHandle = GCHandle.Alloc(easy); IntPtr gcHandlePtr = GCHandle.ToIntPtr(gcHandle); try { Debug.Assert(easy._associatedMultiAgent == null, "A request should only ever be associated with a single agent"); easy._associatedMultiAgent = this; easy.SetCurlOption(CURLoption.CURLOPT_PRIVATE, gcHandlePtr); SetCurlCallbacks(easy, gcHandlePtr); ThrowIfCURLMError(Interop.libcurl.curl_multi_add_handle(multiHandle, easy.EasyHandle)); } catch (Exception exc) { gcHandle.Free(); easy.FailRequest(exc); easy.Cleanup(); // no active processing remains, so cleanup return; } // And if cancellation can be requested, hook up a cancellation callback. // This callback will put the easy request back into the queue and then // ensure that a wake-up request has been issued. When we pull // the easy request out of the request queue, we'll see that it's already // associated with this agent, meaning that it's a cancellation request, // and we'll deal with it appropriately. var cancellationReg = default(CancellationTokenRegistration); if (easy.CancellationToken.CanBeCanceled) { cancellationReg = easy.CancellationToken.Register(s => { var state = (Tuple <MultiAgent, EasyRequest>)s; state.Item1.Queue(state.Item2); state.Item1.RequestWakeup(); }, Tuple.Create <MultiAgent, EasyRequest>(this, easy)); } // Finally, add it to our map. _activeOperations.Add( gcHandlePtr, new ActiveRequest { Easy = easy, CancellationRegistration = cancellationReg }); }
private static void SetSslOptionsForSupportedBackend(EasyRequest easy, ClientCertificateProvider certProvider, IntPtr userPointer) { CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback, userPointer); EventSourceTrace("Callback registration result: {0}", answer, easy: easy); 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; we need to get // the callback from libcurl even if the host name doesn't match, so we take on the responsibility // of doing the host name match in the callback prior to invoking the user's delegate. if (easy._handler.ServerCertificateCustomValidationCallback != null) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSL_VERIFYHOST, 0); // But don't change the CURLOPT_SSL_VERIFYPEER setting, as setting it to 0 will // cause SSL and libcurl to ignore the result of the server callback. } SetSslOptionsForCertificateStore(easy); // 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 SetSslOptionsForUnsupportedBackend(easy, certProvider); break; default: ThrowIfCURLEError(answer); break; } }
private static void HandleRedirectLocationHeader(EasyRequest state, string locationValue) { Debug.Assert(state._isRedirect); Debug.Assert(state._handler.AutomaticRedirection); string location = locationValue.Trim(); //only for absolute redirects Uri forwardUri; if (Uri.TryCreate(location, UriKind.RelativeOrAbsolute, out forwardUri) && forwardUri.IsAbsoluteUri) { // Just as with WinHttpHandler, for security reasons, we drop the server credential if it is // anything other than a CredentialCache. We allow credentials in a CredentialCache since they // are specifically tied to URIs. var creds = state._handler.Credentials as CredentialCache; KeyValuePair <NetworkCredential, CURLAUTH> ncAndScheme = GetCredentials(forwardUri, creds, AuthTypesPermittedByCredentialKind(creds)); if (ncAndScheme.Key != null) { state.SetCredentialsOptions(ncAndScheme); } else { state.SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); } // reset proxy - it is possible that the proxy has different credentials for the new URI state.SetProxyOptions(forwardUri); if (state._handler._useCookie) { // reset cookies. state.SetCurlOption(CURLoption.CURLOPT_COOKIE, IntPtr.Zero); // set cookies again state.SetCookieOption(forwardUri); } state.SetRedirectUri(forwardUri); } // set the headers again. This is a workaround for libcurl's limitation in handling headers with empty values state.SetRequestHeaders(); }
private static void SetSslVersion(EasyRequest easy, IntPtr sslCtx = default(IntPtr)) { // Get the requested protocols. SslProtocols protocols = easy._handler.SslProtocols; if (protocols == SslProtocols.None) { // Let libcurl use its defaults if None is set. return; } // libcurl supports options for either enabling all of the TLS1.* protocols or enabling // just one protocol; it doesn't currently support enabling two of the three, e.g. you can't // pick TLS1.1 and TLS1.2 but not TLS1.0, but you can select just TLS1.2. Interop.Http.CurlSslVersion curlSslVersion; switch (protocols) { #pragma warning disable 0618 // SSL2/3 are deprecated case SslProtocols.Ssl2: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_SSLv2; break; case SslProtocols.Ssl3: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_SSLv3; break; #pragma warning restore 0618 case SslProtocols.Tls: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_0; break; case SslProtocols.Tls11: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_1; break; case SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_2; break; case SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1; break; default: throw new NotSupportedException(SR.net_securityprotocolnotsupported); } try { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)curlSslVersion); } catch (CurlException e) when(e.HResult == (int)CURLcode.CURLE_UNKNOWN_OPTION) { throw new NotSupportedException(SR.net_securityprotocolnotsupported, e); } }
private static void SetSslOptionsForCertificateStore(EasyRequest easy) { // Support specifying certificate directory/bundle via environment variables: SSL_CERT_DIR, SSL_CERT_FILE. GetSslCaLocations(out string sslCaPath, out string sslCaInfo); if (sslCaPath != string.Empty) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAPATH, sslCaPath); // https proxy support requires libcurl 7.52.0+ easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAPATH, sslCaPath); } if (sslCaInfo != string.Empty) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAINFO, sslCaInfo); // https proxy support requires libcurl 7.52.0+ easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAINFO, sslCaInfo); } }
private static void SetSslOptionsForCertificateStore(EasyRequest easy) { // Support specifying certificate directory/bundle via environment variables: SSL_CERT_DIR, SSL_CERT_FILE. string sslCertDir = Environment.GetEnvironmentVariable("SSL_CERT_DIR"); if (sslCertDir != null) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAPATH, sslCertDir); // https proxy support requires libcurl 7.52.0+ easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAPATH, sslCertDir); } string sslCertFile = Environment.GetEnvironmentVariable("SSL_CERT_FILE"); if (sslCertFile != null) { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_CAINFO, sslCertFile); // https proxy support requires libcurl 7.52.0+ easy.TrySetCurlOption(Interop.Http.CURLoption.CURLOPT_PROXY_CAINFO, sslCertFile); } }
private static void SetSslVersion(EasyRequest easy) { // Get the requested protocols. SslProtocols protocols = easy._handler.SslProtocols; if (protocols == SslProtocols.None) { // Let libcurl use its defaults if None is set. return; } // We explicitly disallow choosing SSL2/3. Make sure they were filtered out. Debug.Assert((protocols & ~SecurityProtocol.AllowedSecurityProtocols) == 0, "Disallowed protocols should have been filtered out."); // libcurl supports options for either enabling all of the TLS1.* protocols or enabling // just one of them; it doesn't currently support enabling two of the three, e.g. you can't // pick TLS1.1 and TLS1.2 but not TLS1.0, but you can select just TLS1.2. Interop.Http.CurlSslVersion curlSslVersion; switch (protocols) { case SslProtocols.Tls: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_0; break; case SslProtocols.Tls11: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_1; break; case SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_2; break; case SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1; break; default: throw new NotSupportedException(SR.net_securityprotocolnotsupported); } try { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)curlSslVersion); } catch (CurlException e) when(e.HResult == (int)CURLcode.CURLE_UNKNOWN_OPTION) { throw new NotSupportedException(SR.net_securityprotocolnotsupported, e); } }
internal static void SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) { // Disable SSLv2/SSLv3, allow TLSv1.* easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1); IntPtr userPointer = IntPtr.Zero; if (clientCertOption == ClientCertificateOption.Automatic) { ClientCertificateProvider certProvider = new ClientCertificateProvider(); userPointer = GCHandle.ToIntPtr(certProvider._gcHandle); easy.Task.ContinueWith((_, state) => ((IDisposable)state).Dispose(), certProvider, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } else { Debug.Assert(clientCertOption == ClientCertificateOption.Manual, "ClientCertificateOption is manual or automatic"); } CURLcode answer = easy.SetSslCtxCallback(s_sslCtxCallback, userPointer); switch (answer) { case CURLcode.CURLE_OK: break; // Curl 7.38 and prior case CURLcode.CURLE_UNKNOWN_OPTION: // Curl 7.39 and later case CURLcode.CURLE_NOT_BUILT_IN: EventSourceTrace("CURLOPT_SSL_CTX_FUNCTION not supported. Platform default HTTPS chain building in use"); if (clientCertOption == ClientCertificateOption.Automatic) { throw new PlatformNotSupportedException(SR.net_http_unix_invalid_client_cert_option); } break; default: ThrowIfCURLEError(answer); break; } }
private static void SetCurlCallbacks(EasyRequest easy, IntPtr easyGCHandle) { // Add callback for processing headers easy.SetCurlOption(CURLoption.CURLOPT_HEADERFUNCTION, s_receiveHeadersCallback); easy.SetCurlOption(CURLoption.CURLOPT_HEADERDATA, easyGCHandle); // If we're sending data as part of the request, add callbacks for sending request data if (easy.RequestMessage.Content != null) { easy.SetCurlOption(CURLoption.CURLOPT_READFUNCTION, s_sendCallback); easy.SetCurlOption(CURLoption.CURLOPT_READDATA, easyGCHandle); easy.SetCurlOption(CURLoption.CURLOPT_SEEKFUNCTION, s_seekCallback); easy.SetCurlOption(CURLoption.CURLOPT_SEEKDATA, easyGCHandle); } // If we're expecting any data in response, add a callback for receiving body data if (easy.RequestMessage.Method != HttpMethod.Head) { easy.SetCurlOption(CURLoption.CURLOPT_WRITEFUNCTION, s_receiveBodyCallback); easy.SetCurlOption(CURLoption.CURLOPT_WRITEDATA, easyGCHandle); } }
private static void SetCurlCallbacks(EasyRequest easy, IntPtr easyGCHandle) { // Add callback for processing headers easy.SetCurlOption(CURLoption.CURLOPT_HEADERFUNCTION, s_receiveHeadersCallback); easy.SetCurlOption(CURLoption.CURLOPT_HEADERDATA, easyGCHandle); // If we're sending data as part of the request, add callbacks for sending request data if (easy._requestMessage.Content != null) { easy.SetCurlOption(CURLoption.CURLOPT_READFUNCTION, s_sendCallback); easy.SetCurlOption(CURLoption.CURLOPT_READDATA, easyGCHandle); easy.SetCurlOption(CURLoption.CURLOPT_SEEKFUNCTION, s_seekCallback); easy.SetCurlOption(CURLoption.CURLOPT_SEEKDATA, easyGCHandle); } // If we're expecting any data in response, add a callback for receiving body data if (easy._requestMessage.Method != HttpMethod.Head) { easy.SetCurlOption(CURLoption.CURLOPT_WRITEFUNCTION, s_receiveBodyCallback); easy.SetCurlOption(CURLoption.CURLOPT_WRITEDATA, easyGCHandle); } }
private void ActivateNewRequest(SafeCurlMultiHandle multiHandle, EasyRequest easy) { Debug.Assert(easy != null, "We should never get a null request"); Debug.Assert(easy._associatedMultiAgent == null, "New requests should not be associated with an agent yet"); // If cancellation has been requested, complete the request proactively if (easy._cancellationToken.IsCancellationRequested) { easy.FailRequest(new OperationCanceledException(easy._cancellationToken)); easy.Cleanup(); // no active processing remains, so cleanup return; } // Otherwise, configure it. Most of the configuration was already done when the EasyRequest // was created, but there's additional configuration we need to do specific to this // multi agent, specifically telling the easy request about its own GCHandle and setting // up callbacks for data processing. Once it's configured, add it to the multi handle. GCHandle gcHandle = GCHandle.Alloc(easy); IntPtr gcHandlePtr = GCHandle.ToIntPtr(gcHandle); try { easy._associatedMultiAgent = this; easy.SetCurlOption(CURLoption.CURLOPT_PRIVATE, gcHandlePtr); SetCurlCallbacks(easy, gcHandlePtr); ThrowIfCURLMError(Interop.libcurl.curl_multi_add_handle(multiHandle, easy._easyHandle)); } catch (Exception exc) { gcHandle.Free(); easy.FailRequest(exc); easy.Cleanup(); // no active processing remains, so cleanup return; } // And if cancellation can be requested, hook up a cancellation callback. // This callback will put the easy request back into the queue, which will // ensure that a wake-up request has been issued. When we pull // the easy request out of the request queue, we'll see that it's already // associated with this agent, meaning that it's a cancellation request, // and we'll deal with it appropriately. var cancellationReg = default(CancellationTokenRegistration); if (easy._cancellationToken.CanBeCanceled) { cancellationReg = easy._cancellationToken.Register(s => { var state = (Tuple<MultiAgent, EasyRequest>)s; state.Item1.Queue(new IncomingRequest { Easy = state.Item2, Type = IncomingRequestType.Cancel }); }, Tuple.Create<MultiAgent, EasyRequest>(this, easy)); } // Finally, add it to our map. _activeOperations.Add( gcHandlePtr, new ActiveRequest { Easy = easy, CancellationRegistration = cancellationReg }); }
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); }
private static void SetSslVersion(EasyRequest easy, IntPtr sslCtx = default(IntPtr)) { // Get the requested protocols. System.Security.Authentication.SslProtocols protocols = easy._handler.ActualSslProtocols; // We explicitly disallow choosing SSL2/3. Make sure they were filtered out. Debug.Assert((protocols & ~SecurityProtocol.AllowedSecurityProtocols) == 0, "Disallowed protocols should have been filtered out."); // libcurl supports options for either enabling all of the TLS1.* protocols or enabling // just one of them; it doesn't currently support enabling two of the three, e.g. you can't // pick TLS1.1 and TLS1.2 but not TLS1.0, but you can select just TLS1.2. Interop.Http.CurlSslVersion curlSslVersion; switch (protocols) { case System.Security.Authentication.SslProtocols.Tls: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_0; break; case System.Security.Authentication.SslProtocols.Tls11: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_1; break; case System.Security.Authentication.SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1_2; break; case System.Security.Authentication.SslProtocols.Tls | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls12: curlSslVersion = Interop.Http.CurlSslVersion.CURL_SSLVERSION_TLSv1; break; default: throw new NotSupportedException(SR.net_securityprotocolnotsupported); } try { easy.SetCurlOption(Interop.Http.CURLoption.CURLOPT_SSLVERSION, (long)curlSslVersion); } catch (CurlException e) when (e.HResult == (int)CURLcode.CURLE_UNKNOWN_OPTION) { throw new NotSupportedException(SR.net_securityprotocolnotsupported, e); } }
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. 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; } }
private static void HandleRedirectLocationHeader(EasyRequest state, string locationValue) { Debug.Assert(state._isRedirect); Debug.Assert(state._handler.AutomaticRedirection); string location = locationValue.Trim(); //only for absolute redirects Uri forwardUri; if (Uri.TryCreate(location, UriKind.RelativeOrAbsolute, out forwardUri) && forwardUri.IsAbsoluteUri) { KeyValuePair<NetworkCredential, CURLAUTH> ncAndScheme = GetCredentials(state._handler.Credentials as CredentialCache, forwardUri); if (ncAndScheme.Key != null) { state.SetCredentialsOptions(ncAndScheme); } else { state.SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); } // reset proxy - it is possible that the proxy has different credentials for the new URI state.SetProxyOptions(forwardUri); if (state._handler._useCookie) { // reset cookies. state.SetCurlOption(CURLoption.CURLOPT_COOKIE, IntPtr.Zero); // set cookies again state.SetCookieOption(forwardUri); } } }
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader, EasyRequest state) { if (!responseHeader.StartsWith(s_httpPrefix, StringComparison.OrdinalIgnoreCase)) { return(false); } // Clear the header if status line is recieved again. This signifies that there are multiple response headers (like in redirection). response.Headers.Clear(); response.Content.Headers.Clear(); int responseHeaderLength = responseHeader.Length; // Check if line begins with HTTP/1.1 or HTTP/1.0 int prefixLength = s_httpPrefix.Length; int versionIndex = prefixLength + 2; if ((versionIndex < responseHeaderLength) && (responseHeader[prefixLength] == '1') && (responseHeader[prefixLength + 1] == '.')) { response.Version = responseHeader[versionIndex] == '1' ? HttpVersion.Version11 : responseHeader[versionIndex] == '0' ? HttpVersion.Version10 : new Version(0, 0); } else { response.Version = new Version(0, 0); } // TODO: Parsing errors are treated as fatal. Find right behaviour int spaceIndex = responseHeader.IndexOf(SpaceChar); if (spaceIndex > -1) { int codeStartIndex = spaceIndex + 1; int statusCode = 0; // Parse first 3 characters after a space as status code if (TryParseStatusCode(responseHeader, codeStartIndex, out statusCode)) { response.StatusCode = (HttpStatusCode)statusCode; // For security reasons, we drop the server credential if it is a // NetworkCredential. But we allow credentials in a CredentialCache // since they are specifically tied to URI's. if ((response.StatusCode == HttpStatusCode.Redirect) && !(state.Handler.Credentials is CredentialCache)) { state.SetCurlOption(CURLoption.CURLOPT_HTTPAUTH, CURLAUTH.None); state.SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); state.NetworkCredential = null; } int codeEndIndex = codeStartIndex + StatusCodeLength; int reasonPhraseIndex = codeEndIndex + 1; if (reasonPhraseIndex < responseHeaderLength && responseHeader[codeEndIndex] == SpaceChar) { int newLineCharIndex = responseHeader.IndexOfAny(s_newLineCharArray, reasonPhraseIndex); int reasonPhraseEnd = newLineCharIndex >= 0 ? newLineCharIndex : responseHeaderLength; response.ReasonPhrase = responseHeader.Substring(reasonPhraseIndex, reasonPhraseEnd - reasonPhraseIndex); } } } return(true); }
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. 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); 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; } }
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader, EasyRequest state) { if (!responseHeader.StartsWith(HttpPrefix, StringComparison.OrdinalIgnoreCase)) { return false; } // Clear the header if status line is recieved again. This signifies that there are multiple response headers (like in redirection). response.Headers.Clear(); response.Content.Headers.Clear(); int responseHeaderLength = responseHeader.Length; // Check if line begins with HTTP/1.1 or HTTP/1.0 int prefixLength = HttpPrefix.Length; int versionIndex = prefixLength + 2; if ((versionIndex < responseHeaderLength) && (responseHeader[prefixLength] == '1') && (responseHeader[prefixLength + 1] == '.')) { response.Version = responseHeader[versionIndex] == '1' ? HttpVersion.Version11 : responseHeader[versionIndex] == '0' ? HttpVersion.Version10 : new Version(0, 0); } else { response.Version = new Version(0, 0); } // TODO: Parsing errors are treated as fatal. Find right behaviour int spaceIndex = responseHeader.IndexOf(SpaceChar); if (spaceIndex > -1) { int codeStartIndex = spaceIndex + 1; int statusCode = 0; // Parse first 3 characters after a space as status code if (TryParseStatusCode(responseHeader, codeStartIndex, out statusCode)) { response.StatusCode = (HttpStatusCode)statusCode; // For security reasons, we drop the server credential if it is a // NetworkCredential. But we allow credentials in a CredentialCache // since they are specifically tied to URI's. if ((response.StatusCode == HttpStatusCode.Redirect) && !(state._handler.Credentials is CredentialCache)) { state.SetCurlOption(CURLoption.CURLOPT_HTTPAUTH, CURLAUTH.None); state.SetCurlOption(CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.SetCurlOption(CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); state._networkCredential = null; } int codeEndIndex = codeStartIndex + StatusCodeLength; int reasonPhraseIndex = codeEndIndex + 1; if (reasonPhraseIndex < responseHeaderLength && responseHeader[codeEndIndex] == SpaceChar) { int newLineCharIndex = responseHeader.IndexOfAny(s_newLineCharArray, reasonPhraseIndex); int reasonPhraseEnd = newLineCharIndex >= 0 ? newLineCharIndex : responseHeaderLength; response.ReasonPhrase = responseHeader.Substring(reasonPhraseIndex, reasonPhraseEnd - reasonPhraseIndex); } } } return true; }