Ответ 1
Вы можете использовать Reflection, чтобы получить значение TlsStream->SslState->SslProtocol
.
Эта информация может быть извлечена из потока, возвращаемого как HttpWebRequest.GetRequestStream()
и HttpWebRequest.GetResponseStream()
.
ExtractSslProtocol()
также обрабатывает сжатые GzipStream
или DeflateStream
, которые возвращаются при активации автоматического восстановления WebRequest
.
Проверка будет происходить в ServerCertificateValidationCallback
, который вызывается, когда запрос инициализируется с помощью request.GetRequestStream()
Примечание. SecurityProtocolType.Tls13
включен в .Net Framework 4.8+
и .Net Core 3.0+
.
using System.Net;
using System.Net.Security;
using System.Reflection;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
//(...)
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 |
SecurityProtocolType.Tls |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls12 |
SecurityProtocolType.Tls13;
ServicePointManager.ServerCertificateValidationCallback += TlsValidationCallback;
HttpWebRequest request = WebRequest.CreateHttp(decodedUri);
using (Stream requestStream = request.GetRequestStream()) {
//Here the request stream is already validated
SslProtocols sslProtocol = ExtractSslProtocol(requestStream);
if (sslProtocol < SslProtocols.Tls12)
{
// Refuse/close the connection
}
}
//(...)
private SslProtocols ExtractSslProtocol(Stream stream)
{
if (stream is null) return SslProtocols.None;
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
Stream metaStream = stream;
if (stream.GetType().BaseType == typeof(GZipStream)) {
metaStream = (stream as GZipStream).BaseStream;
}
else if (stream.GetType().BaseType == typeof(DeflateStream)) {
metaStream = (stream as DeflateStream).BaseStream;
}
var connection = metaStream.GetType().GetProperty("Connection", bindingFlags).GetValue(metaStream);
if (!(bool)connection.GetType().GetProperty("UsingSecureStream", bindingFlags).GetValue(connection)) {
// Not a Https connection
return SslProtocols.None;
}
var tlsStream = connection.GetType().GetProperty("NetworkStream", bindingFlags).GetValue(connection);
var tlsState = tlsStream.GetType().GetField("m_Worker", bindingFlags).GetValue(tlsStream);
return (SslProtocols)tlsState.GetType().GetProperty("SslProtocol", bindingFlags).GetValue(tlsState);
}
RemoteCertificateValidationCallback
содержит полезную информацию об используемых протоколах безопасности. (см. Параметры безопасности транспортного уровня (TLS) (IANA) и RFC 5246).
Используемые типы протоколов безопасности могут быть достаточно информативными, поскольку каждая версия протокола поддерживает подмножество алгоритмов хеширования и шифрования.
Tls 1.2 представляет HMAC-SHA256
и не поддерживает шифры IDEA
и DES
(все варианты перечислены в связанных документах).
Здесь я вставил OIDExtractor
, который перечисляет используемые алгоритмы.
Обратите внимание, что и TcpClient(), и WebRequest() попадут сюда.
private bool TlsValidationCallback(object sender, X509Certificate CACert, X509Chain CAChain, SslPolicyErrors sslPolicyErrors)
{
List<Oid> oidExtractor = CAChain
.ChainElements
.Cast<X509ChainElement>()
.Select(x509 => new Oid(x509.Certificate.SignatureAlgorithm.Value))
.ToList();
// Inspect the oidExtractor list
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
X509Certificate2 certificate = new X509Certificate2(CACert);
//If you needed/have to pass a certificate, add it here.
//X509Certificate2 cert = new X509Certificate2(@"[localstorage]/[ca.cert]");
//CAChain.ChainPolicy.ExtraStore.Add(cert);
CAChain.Build(certificate);
foreach (X509ChainStatus CACStatus in CAChain.ChainStatus)
{
if ((CACStatus.Status != X509ChainStatusFlags.NoError) &
(CACStatus.Status != X509ChainStatusFlags.UntrustedRoot))
return false;
}
return true;
}
ОБНОВЛЕНИЕ 2:
Метод
secur32.dll
→ QueryContextAttributesW()
позволяет запрашивать контекст безопасности подключения инициализированного потока. [DllImport("secur32.dll", CharSet = CharSet.Auto, ExactSpelling=true, SetLastError=false)]
private static extern int QueryContextAttributesW(SSPIHandle contextHandle,
[In] ContextAttribute attribute,
[In] [Out] ref SecPkgContext_ConnectionInfo ConnectionInfo);
Как видно из документации, этот метод возвращает void* buffer
который ссылается на структуру SecPkgContext_ConnectionInfo
:
//[SuppressUnmanagedCodeSecurity]
private struct SecPkgContext_ConnectionInfo
{
public SchProtocols dwProtocol;
public ALG_ID aiCipher;
public int dwCipherStrength;
public ALG_ID aiHash;
public int dwHashStrength;
public ALG_ID aiExch;
public int dwExchStrength;
}
SchProtocols dwProtocol
является SslProtocol.
В чем подвох. TlsStream.Context.m_SecurityContext._handle
который ссылается на TlsStream.Context.m_SecurityContext._handle
контекста соединения, не является общедоступным.
Таким образом, вы можете получить его, опять же, только через отражение или через System.Net.Security.AuthenticatedStream
производных классов (System.Net.Security.SslStream
и System.Net.Security.NegotiateStream
) возвращаемые TcpClient.GetStream()
.
К сожалению, поток, возвращаемый WebRequest/WebResponse, не может быть приведен к этим классам. Типы соединений и потоков доступны только через непубличные свойства и поля.
Я публикую собранную документацию, возможно, она поможет вам найти другой путь, чтобы добраться до этого дескриптора контекста.
Объявления, структуры, списки перечислителей находятся в QueryContextAttributesW (PASTEBIN).
Microsoft TechNet
Структуры аутентификации
MSDN
Создание безопасного соединения с использованием Schannel
Получение информации о соединениях Schannel
Запрос атрибутов контекста канала
QueryContextAttributes (Schannel)
Кодовая база (частичная)
внутренняя структура SSPIHandle {}
внутреннее перечисление ContextAttribute {}
ОБНОВЛЕНИЕ 1:
В вашем комментарии к другому ответу я увидел, что решение с использованием
TcpClient()
неприемлемо для вас. В любом случае я оставляю это здесь, так что комментарии Бена Фойгта в этом будут полезны всем, кто заинтересован. Кроме того, 3 возможных решения лучше, чем 2.
Предоставлены некоторые подробности реализации использования TcpClient() SslStream в контексте.
Если перед инициализацией WebRequest требуются данные протокола, соединение TcpClient() может быть установлено в том же контексте с использованием тех же инструментов, которые необходимы для соединения TLS. А именно, ServicePointManager.SecurityProtocol
для определения поддерживаемых протоколов и ServicePointManager.ServerCertificateValidationCallback
для проверки сертификата сервера.
И TcpClient(), и WebRequest могут использовать эти параметры:
- включить все протоколы и позволить Tls Handshake определить, какой из них будет использоваться.
- определить RemoteCertificateValidationCallback()
для проверки X509Certificates
Сервер передает в X509Chain
.
На практике Tls Handshake одинакова при установлении соединения TcpClient или WebRequest.
Этот подход позволяет узнать, какой протокол Tls ваш HttpWebRequest будет согласовывать с тем же сервером.
Установите TcpClient()
для получения и оценки SslStream
.
Флаг checkCertificateRevocation
имеет значение false
, поэтому процесс не будет тратить время на поиск списка отзыва.
Обратный вызов проверки сертификата такой же, как указано в ServicePointManager
TlsInfo TLSInfo;
IPHostEntry DnsHost = await Dns.GetHostEntryAsync(HostURI.Host);
using (TcpClient client = new TcpClient(DnsHost.HostName, 443))
{
using (SslStream sslstream = new SslStream(client.GetStream(), false,
TlsValidationCallback, null))
{
sslstream.AuthenticateAsClient(DnsHost.HostName, null,
(SslProtocols)ServicePointManager.SecurityProtocol, false);
TLSInfo = new TlsInfo(sslstream);
}
}
//The HttpWebRequest goes on from here.
HttpWebRequest httpRequest = WebRequest.CreateHttp(HostURI);
//(...)
Класс TlsInfo
собирает некоторую информацию об установленном безопасном соединении:
- версия протокола Tls
- Алгоритмы шифрования и хэширования
- Сертификат сервера, используемый в Ssl Handshake
public class TlsInfo
{
public TlsInfo(SslStream SecureStream)
{
this.ProtocolVersion = SecureStream.SslProtocol;
this.CipherAlgorithm = SecureStream.CipherAlgorithm;
this.HashAlgorithm = SecureStream.HashAlgorithm;
this.RemoteCertificate = SecureStream.RemoteCertificate;
}
public SslProtocols ProtocolVersion { get; set; }
public CipherAlgorithmType CipherAlgorithm { get; set; }
public HashAlgorithmType HashAlgorithm { get; set; }
public X509Certificate RemoteCertificate { get; set; }
}