您可以使用反射来获取
TlsStream->SslState->SslProtocol
属性的值。
这些信息可以从由
HttpWebRequest.GetRequestStream()
和
HttpWebRequest.GetResponseStream()
返回的流中提取。
ExtractSslProtocol()
还处理启用了
WebRequest
AutomaticDecompression时返回的压缩
GzipStream
或
DeflateStream
。
验证将在
ServerCertificateValidationCallback
中进行,在使用
request.GetRequestStream()
初始化请求时调用。
注意:
SecurityProtocolType.Tls13
包含在 .Net Framework
4.8+
和.Net Core
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;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 |
SecurityProtocolType.Tls |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls12 |
SecurityProtocolType.Tls13;
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()) {
SslProtocols sslProtocol = ExtractSslProtocol(requestStream);
if (sslProtocol < SslProtocols.Tls12)
{
}
}
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)) {
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();
var certificate = new X509Certificate2(CACert);
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
)
根据文档,该方法返回一个引用SecPkgContext_ConnectionInfo
结构的void*缓冲区
:
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
并不是公开的。因此,您只能通过反射或通过TcpClient.GetStream()
返回的System.Net.Security.AuthenticatedStream
派生类(System.Net.Security.SslStream
和System.Net.Security.NegotiateStream
)获取它。
不幸的是,由WebRequest/WebResponse返回的流无法转换为这些类。连接和流类型仅通过非公开属性和字段引用。
我正在发布组装好的文档,它可能会帮助您找到另一条路径来获取到该上下文句柄。
声明、结构体和枚举列表可以在QueryContextAttributesW (PASTEBIN)中找到。
Microsoft TechNet
身份验证结构
MSDN
使用Schannel创建安全连接
获取有关Schannel连接的信息
查询Schannel上下文的属性
QueryContextAttributes (Schannel)
代码库(部分)
.NET参考源代码
Internals.cs
内部结构体SSPIHandle { }
内部枚举类型ContextAttribute { }
更新 1:
我看到你在对另一个答案的评论中提到使用TcpClient()
的解决方案对你来说不可接受。我还是把它留在这里,以便Ben Voigt在此回答中的评论对其他人有用。此外,三种可能的解决方案总比两种好。
在提供的上下文中,关于TcpClient()和SslStream的一些实现细节。
如果在初始化WebRequest之前需要协议信息,则可以在同一上下文中使用与TLS连接所需相同的工具建立TcpClient()连接。即,使用ServicePointManager.SecurityProtocol
定义支持的协议和ServicePointManager.ServerCertificateValidationCallback
验证服务器证书。
TcpClient()和WebRequest都可以使用这些设置:
- 启用所有协议,让TLS握手确定将使用哪个协议。
- 定义
RemoteCertificateValidationCallback()
委托以验证服务器传递的X509Certificates。
在实践中,建立TcpClient或WebRequest连接时,TLS握手相同。
这种方法可以让您知道您的HttpWebRequest将与同一服务器协商哪种Tls协议。
设置一个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
类收集建立安全连接时的一些信息:
- TLS协议版本
- 密码和散列算法
- 用于SSL握手的服务器证书
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; }
}
System.Net
跟踪,它将记录这些信息,然后你可能可以解析出来,例如:System.Net 信息: 0 : [18984] EndProcessAuthentication(Protocol=Tls12, Cipher=Aes256 256 bit strength,....等等..
- CrowcoderSslStream
之上自己重新实现HTTP的方式来获取。这可能值得为其提交一个问题(issue)。 - Jeroen Mostert