我的应用程序在 .NET 4.7 中运行。默认情况下,它将尝试使用 TLS1.2。
是否可以知道在执行以下 HTTP 请求时协商了哪个 TLS 版本?
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(decodedUri);
if (requestPayload.Length > 0)
{
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(requestPayload, 0, requestPayload.Length);
}
}
我只需要此信息用于记录/调试目的,因此在写入请求流或接收响应之前获得此信息并不重要。我不想为这些信息解析网络跟踪日志,我也不想创建第二个连接(使用 SslStream 或类似的)。
最佳答案
您可以使用反射到达 TlsStream->SslState->SslProtocol
适当的值(value)。
此信息可以从 HttpWebRequest.GetRequestStream()
返回的 Stream 中提取。和 HttpWebRequest.GetResponseStream()
.ExtractSslProtocol()
还处理压缩的 GzipStream
或 DeflateStream
WebRequest
时返回的AutomaticDecompression被激活。
验证将在 ServerCertificateValidationCallback
中进行,在使用 request.GetRequestStream()
初始化请求时调用
备注 : SecurityProtocolType.Tls13
包含在 .Net Framework 4.8+
中和 .Net 核心 3.0+
.
using System.IO.Compression;
using System.Net;
using System.Net.Security;
using System.Reflection;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
//(...)
// Allow all, to then check what the Handshake will agree upon
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 |
SecurityProtocolType.Tls |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls12 |
SecurityProtocolType.Tls13;
// Handle the Server certificate exchange, to inspect the certificates received
ServicePointManager.ServerCertificateValidationCallback += TlsValidationCallback;
Uri requestUri = new Uri("https://somesite.com");
var request = WebRequest.CreateHttp(requestUri);
request.Method = WebRequestMethods.Http.Post;
request.ServicePoint.Expect100Continue = false;
request.AllowAutoRedirect = true;
request.CookieContainer = new CookieContainer();
request.ContentType = "application/x-www-form-urlencoded";
var postdata = Encoding.UTF8.GetBytes("Some postdata here");
request.ContentLength = postdata.Length;
request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident / 7.0; rv: 11.0) like Gecko";
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip, deflate;q=0.8");
request.Headers.Add(HttpRequestHeader.CacheControl, "no-cache");
using (var 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
有一些关于使用的安全协议(protocol)的有用信息。 (参见:Transport Layer Security (TLS) Parameters (IANA) 和 RFC 5246)。使用的安全协议(protocol)类型可以提供足够的信息,因为每个协议(protocol)版本都支持散列和加密算法的子集。
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
var 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
结构体: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
引用连接上下文句柄的不是公开的。因此,您只能通过反射或通过
System.Net.Security.AuthenticatedStream
再次获得它。 System.Net.Security.SslStream
返回的派生类( System.Net.Security.NegotiateStream
和 TcpClient.GetStream()
) .不幸的是,WebRequest/WebResponse 返回的 Stream 不能转换为这些类。 Connections 和 Streams 类型仅通过非公共(public)属性和字段引用。
我正在发布汇编的文档,它可能会帮助您找出到达该上下文句柄的另一条路径。
声明、结构、枚举器列表位于 QueryContextAttributesW (PASTEBIN) .
微软技术网
Authentication Structures
微软
Creating a Secure Connection Using Schannel
Getting Information About Schannel Connections
Querying the Attributes of an Schannel Context
QueryContextAttributes (Schannel)
代码库(部分)
.NET Reference Source
Internals.cs
internal struct SSPIHandle { }
internal enum ContextAttribute { }
更新 1:
I saw in your comment to another answer that the solution using
TcpClient()
is not acceptable for you. I'm leaving it here anyway so the comments of Ben Voigt in this one will be useful to anyone else interested. Also, 3 possible solutions are better than 2.
TcpClient() 上的一些实现细节SslStream在提供的上下文中使用。
如果在初始化 WebRequest 之前需要协议(protocol)信息,则可以使用 TLS 连接所需的相同工具在相同的上下文中建立 TcpClient() 连接。即
ServicePointManager.SecurityProtocol
定义支持的协议(protocol)和 ServicePointManager.ServerCertificateValidationCallback
验证服务器证书。TcpClient() 和 WebRequest 都可以使用这些设置:
RemoteCertificateValidationCallback()
委托(delegate)验证 X509Certificates
服务器传入 X509Chain
. 实际上,在建立 TcpClient 或 WebRequest 连接时,TLS 握手是相同的。
这种方法让您知道您的 HttpWebRequest 是什么 Tls 协议(protocol)将与同一服务器协商。
设置
TcpClient()
接收和评估SslStream
.checkCertificateRevocation
标志设置为 false
,因此该过程不会浪费时间查找吊销列表。证书验证回调与
ServicePointManager
中指定的相同.TlsInfo tlsInfo = null;
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
类收集有关已建立的安全连接的一些信息:public class TlsInfo
{
public TlsInfo(SslStream secStream)
{
this.ProtocolVersion = secStream.SslProtocol;
this.CipherAlgorithm = secStream.CipherAlgorithm;
this.HashAlgorithm = secStream.HashAlgorithm;
this.RemoteCertificate = secStream.RemoteCertificate;
}
public SslProtocols ProtocolVersion { get; set; }
public CipherAlgorithmType CipherAlgorithm { get; set; }
public HashAlgorithmType HashAlgorithm { get; set; }
public X509Certificate RemoteCertificate { get; set; }
}
关于c# - 协商了哪个 TLS 版本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48589590/