Установите SecurityProtocol (Ssl3 или TLS) в.net HttpWebRequest для каждого запроса.
Мое приложение (.net 3.5 sp1) использует HttpWebRequest для связи с разными конечными точками, иногда через HTTPS, где у каждого хост-сервера могут быть разные требования к протоколу безопасности, например, TLS или SSL3, или и то и другое.
Обычно серверы играют хорошо и с радостью договариваются / отказываются от того, какой SecurityProtocol использует TLS или SSL3, но некоторые этого не делают, и когда.net настроен как TLS или SSL3 (по умолчанию, я думаю), те серверы, которые поддерживают только SSL3, вызывают.net бросить ошибку отправки.
Из того, что я могу сказать,.net предоставляет объекту ServicePointManager свойство SecurityProtocol, которое может быть установлено в TLS, SSL3 или оба. Следовательно, в идеале, когда задано и то и другое, идея заключается в том, что клиент и сервер должны договариваться о том, что использовать, но, как уже было сказано, это не работает.
Предположительно вы могли бы установить ServicePointManager.SecurityProtocol = Ssl3, но как насчет конечных точек, которые хотят использовать TLS?
Проблема, которую я вижу с ServicePointManager и SecurityProtocol, заключается в том, что он статичен и, следовательно, имеет широкую область применения.
Так что к вопросу..
Как мне использовать HttpWebRequest с другим SecurityProtocol, например
1) URL 1 установлен для использования TLS | Ssl3 (переговоры)
2) URL 2 установлен на Ssl3 (только Ssl3)
12 ответов
К сожалению, не похоже, что вы можете настроить это для каждой точки обслуживания. Я бы посоветовал вам подать запрос на добавление функций на веб-сайте MS Connect для этой области.
В качестве грязного обходного пути вы можете попробовать запустить сайты, для которых требуется другой протокол безопасности, в новом домене приложения. Статические экземпляры для каждого домена приложения, так что это должно дать вам необходимую изоляцию.
У меня была та же проблема, и я написал прокси-класс, который открывает порт на локальном хосте и перенаправляет весь трафик на указанный хост: порт.
так что соединение идет так
[ваш код] --- HTTP ---> [прокси на локальном хосте: порт] --- HTTPS ---> [веб-сайт]
фактически его можно использовать для переноса любого протокола в SSL/TLS, а не только в HTTP
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
namespace System
{
class sslProxy : IDisposable
{
readonly string host;
readonly int port;
readonly TcpListener listener;
readonly SslProtocols sslProtocols;
bool disposed;
static readonly X509CertificateCollection sertCol = new X509CertificateCollection();
public sslProxy(string url, SslProtocols protocols)
{
var uri = new Uri(url);
host = uri.Host;
port = uri.Port;
sslProtocols = protocols;
listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
listener.BeginAcceptTcpClient(onAcceptTcpClient, null);
Proxy = new WebProxy("localhost", (listener.LocalEndpoint as IPEndPoint).Port);
}
public WebProxy Proxy
{
get;
private set;
}
class stBuf
{
public TcpClient tcs;
public TcpClient tcd;
public Stream sts;
public Stream std;
public byte[] buf;
public stBuf dup;
}
void onAcceptTcpClient(IAsyncResult ar)
{
if (disposed) return;
var tcl = listener.EndAcceptTcpClient(ar);
TcpClient tcr = null;
try
{
listener.BeginAcceptTcpClient(onAcceptTcpClient, null);
var nsl = tcl.GetStream();
tcr = new TcpClient(host, port);
Stream nsr = tcr.GetStream();
if (sslProtocols != SslProtocols.None)
{
var sss = new SslStream(nsr, true);
sss.AuthenticateAsClient(host, sertCol, sslProtocols, false);
nsr = sss;
} // if
var sts = new stBuf() { tcs = tcl, sts = nsl, tcd = tcr, std = nsr, buf = new byte[tcl.ReceiveBufferSize] };
var std = new stBuf() { tcs = tcr, sts = nsr, tcd = tcl, std = nsl, buf = new byte[tcr.ReceiveBufferSize] };
sts.dup = std;
std.dup = sts;
nsl.BeginRead(sts.buf, 0, sts.buf.Length, onReceive, sts);
nsr.BeginRead(std.buf, 0, std.buf.Length, onReceive, std);
} // try
catch
{
tcl.Close();
if (tcr != null) tcr.Close();
} // catch
}
void close(stBuf st)
{
var dup = st.dup;
if (dup != null)
{
dup.dup = st.dup = null;
st.sts.Dispose();
st.std.Dispose();
} // if
}
void onReceive(IAsyncResult ar)
{
var st = ar.AsyncState as stBuf;
try
{
if (!(st.dup != null && st.tcs.Connected && st.sts.CanRead && !disposed)) { close(st); return; };
var n = st.sts.EndRead(ar);
if (!(n > 0 && st.tcd.Connected && st.std.CanWrite)) { close(st); return; };
st.std.Write(st.buf, 0, n);
if (!(st.tcs.Connected && st.tcd.Connected && st.sts.CanRead && st.std.CanWrite)) { close(st); return; };
st.sts.BeginRead(st.buf, 0, st.buf.Length, onReceive, st);
} // try
catch
{
close(st);
} // catch
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
listener.Stop();
} // if
}
}
}
пример использования
// create proxy once and keep it
// note you have to mention :443 port (https default)
// ssl protocols to use (enum can use | or + to have many)
var p = new sslProxy("http://www.google.com:443", SslProtocols.Tls);
// using our connections
for (int i=0; i<5; i++)
{
// url here goes without https just http
var rq = HttpWebRequest.CreateHttp("http://www.google.com/") as HttpWebRequest;
// specify that we are connecting via proxy
rq.Proxy = p.Proxy;
var rs = rq.GetResponse() as HttpWebResponse;
var r = new StreamReader(rs.GetResponseStream()).ReadToEnd();
rs.Dispose();
} // for
// just dispose proxy once done
p.Dispose();
После того, как некоторые из наших поставщиков прекратили поддержку ssl3, в то время как другие используют ее исключительно, в нашей системе появилось много проблем, которые могли бы быть решены с помощью функциональности из этого вопроса. Но шесть лет спустя у нас все еще нет встроенного механизма для достижения этой цели. Наш обходной путь заключается в явном определении протокола безопасности, который будет поддерживать все сценарии, например:
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Ssl3
| System.Net.SecurityProtocolType.Tls12
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls;
В соответствии с этим ответом параметр SecurityProtocol фактически относится к домену приложений, поэтому вы можете, если вы решили, что он будет работать, создать отдельные домены приложений для отдельных параметров и распределить запросы по всем параметрам.
Не совсем "аккуратное" решение, но может просто сделать то, что вам нужно, без обращения к сторонним библиотекам.
Я столкнулся с той же проблемой и нашел решение, использующее отражение. В исходном коде для HttpWebRequest вы можете найти внутреннее свойство SslProtocols, которое устанавливается в конструкторе во время создания с текущим значением из ServicePointManager.SecurityProtocol . Итак, после создания HttpWebRequest и перед его выполнением установите для этого свойства соответствующий протокол безопасности :
var request = (HttpWebRequest)WebRequest.Create(endpoint);
typeof(HttpWebRequest)
.GetProperty("SslProtocols", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(request, System.Security.Authentication.SslProtocols.Tls12);
Начиная с Net 4.5, для Windows (от Microsoft) есть пакет HgetpClient и WinHttpHandler, позволяющий устанавливать параметры SslProtocols. Вы можете использовать класс HttpClientHandler для сетевого ядра.
using (var hc = new HttpClient(new WinHttpHandler() // should have it as a static member
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
SslProtocols = SslProtocols.Tls |
SslProtocols.Tls11 |
SslProtocols.Tls12
}))
{
var r = hc.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://..."));
r.Wait();
Console.WriteLine(r.Result.StatusCode);
} // using
Вы можете достичь этого с помощью этого кода, чтобы закрыть все базовые соединения и вызвать новое рукопожатие.
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
...
...
...
request.ServicePoint.CloseConnectionGroup(request.ConnectionGroupName);
Я знаю, что этот вопрос старый, но проблема остается даже с.Net 4.7.2. В моем случае у меня есть многопоточное приложение, которое взаимодействует с двумя конечными точками. Одна конечная точка работает только с TLS 1.2, а другая конечная точка работает только с TLS 1.0 (команда, ответственная за это, работает над исправлением своей конечной точки, поэтому она также будет поддерживать TLS 1.2).
Чтобы обойти это, я переместил вызовы службы для конечной точки, которая работает только с TLS 1.0, в отдельный класс в той же сборке, а затем загрузил сборку в отдельный домен приложения. Делая это, я могу использовать эту глобальную переменную:
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
только для вызовов к сломанной конечной точке, в то же время продолжая работать и с вызовами к конечной точке TLS 1.2 (для которых не требуется устанавливать ServicePointManager.SecurityProtocol что-либо конкретное). Это также гарантирует, что когда хорошая конечная точка будет обновлена до TLS 1.3, мне не нужно будет повторно выпускать мое приложение. Мое приложение многопоточное и имеет большую емкость, поэтому блокировка или попытка / окончание не являются адекватными решениями.
Вот код, который я использовал для загрузки сборки в отдельный домен. Обратите внимание, что я загружаю сборку из ее текущего местоположения (временные файлы Aspnet), чтобы она не блокировала сборку в каталоге bin и не блокировала будущие развертывания.
Также обратите внимание, что прокси-класс наследует MarshalByRefObject, поэтому он используется в качестве прозрачного прокси, который сохраняет System.Net.ServicePointManager со своим собственным значением в своем собственном домене приложений.
Это кажется глупым ограничением со стороны.Net Framework, я хотел бы, чтобы мы могли просто указать протокол непосредственно в веб-запросе, а не прыгать через обручи, особенно после нескольких лет этого.:(
Этот код работает, надеюсь, он вам поможет!:)
private static AppDomain _proxyDomain = null;
private static Object _syncObject = new Object();
public void MakeACallToTls10Endpoint(string tls10Endpoint, string jsonRequest)
{
if (_proxyDomain == null)
{
lock(_syncObject) // Only allow one thread to spin up the app domain.
{
if (_proxyDomain == null)
{
_proxyDomain = AppDomain.CreateDomain("CommunicationProxyDomain");
}
}
}
Type communicationProxyType = typeof(CommunicationProxy);
string assemblyPath = communicationProxyType.Assembly.Location;
// Always loading from the current assembly, sometimes this moves around in ASPNet Tempfiles causing type not found errors if you make it static.
ObjectHandle objectHandle = _proxyDomain.CreateInstanceFrom(assemblyPath, communicationProxyType.FullName.Split(',')[0]);
CommunicationProxy communicationProxy = (CommunicationProxy)objectHandle.Unwrap();
return communicationProxy.ExecuteHttpPost(tls10Endpoint, jsonRequest);
}
Затем в отдельный класс:
[Serializable]
public class CommunicationProxy : MarshalByRefObject
{
public string ExecuteHttpPost(string tls10Endpoint, string jsonRequest)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
// << Bunch of code to do the request >>
}
}
Вы можете создать HttpWebRequest "служебный класс" с помощью статического служебного метода для выполнения HttpWebRequests. В методе статической утилиты используйте оператор C# lock для настройки ServicePointManager.SecurityProtocol и создания определенного HttpWebRequest. Оператор блокировки не позволяет другим потокам из того же AppDomain выполнять тот же код в одно и то же время, поэтому только что установленный протокол TLS не будет изменен, пока не будет выполнен весь блок блокировки (= критическая секция).
Но помните, что для действительно высокопроизводительных приложений (чрезвычайно высокопроизводительных!) Такой подход может оказать негативное влияние на производительность.
Установите все это. В моем приложении это работа для разных протоколов безопасности.
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Ssl3
| System.Net.SecurityProtocolType.Tls12
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls;
Немного упрощенно. |= важно.
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
Если вы используете WCF
System.ServiceModel
это невозможно сделать в .net4.8, но в версии .netCore есть такая возможность.
Вы не можете полностью заменить, так как отсутствуют многие компоненты, но это может работать только для простых вызовов некоторых API.
Вы можете загрузить обе версии в один и тот же проект, но вам нужно использовать
extern alias
разрешать конфликты имен.
Установка
Сначала вам нужно установить NuGet в проект.
Изменить свойства в проекте
References
и установить
Aliases
например
ServiceModelHack
.
Обновление веб-сервиса
В
Reference.cs
данного веб-сервиса в проекте
Service References
Для этого примера веб-сервис называется
TestService
, то файл должен быть в
Service References\TestService\Reference.cs
Добавить в начало файла строку
extern alias ServiceModelHack;
И запустите замену всего текста в файле:
System.ServiceModel.
->
ServiceModelHack::System.ServiceModel.
Вызов веб-сервиса
В качестве
System.Private.ServiceModel
отказаться от некоторых функций, которые вы не можете использовать
App.config
напрямую, вам нужно вручную установить привязки, например:
var binding = new ServiceModelHack::System.ServiceModel.BasicHttpsBinding
{
Security = new ServiceModelHack::System.ServiceModel.BasicHttpsSecurity
{
Transport = new ServiceModelHack::System.ServiceModel.HttpTransportSecurity
{
}
}
};
var endpoint = new ServiceModelHack::System.ServiceModel.EndpointAddress("https://example.com/ws");
using (var service = new TestService(binding, endpoint))
{
Helper.setTls(service, SslProtocols.Tls13); //describe in next part
service.Ping();
}
Изменение версии TLS
на основе: https://github.com/dotnet/wcf/issues/3442#issuecomment-475356182
public static void setTls<T>(ServiceModelHack::System.ServiceModel.ClientBase<T> client, SslProtocols ssl) where T : class
{
client.Endpoint.EndpointBehaviors.Add(
new SslProtocolEndpointBehavior
{
SslProtocols = ssl,
}
);
}
public class SslProtocolEndpointBehavior : ServiceModelHack::System.ServiceModel.Description.IEndpointBehavior
{
public SslProtocols SslProtocols { get; set; } = SslProtocols.Tls12;
public void AddBindingParameters(ServiceModelHack::System.ServiceModel.Description.ServiceEndpoint endpoint, ServiceModelHack::System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(x =>
{
x.SslProtocols = this.SslProtocols;
return x; // You can just return the modified HttpClientHandler
}));
}
public void ApplyClientBehavior(ServiceModelHack::System.ServiceModel.Description.ServiceEndpoint endpoint, ServiceModelHack::System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { }
public void ApplyDispatchBehavior(ServiceModelHack::System.ServiceModel.Description.ServiceEndpoint endpoint, ServiceModelHack::System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceModelHack::System.ServiceModel.Description.ServiceEndpoint endpoint) { }
}