您可以使用反射来获取
TlsStream->SslState->SslProtocol属性值。
这些信息可以从双方返回的流中提取
HttpWebRequest.GetRequestStream()和
HttpWebRequest.GetResponseStream()。
该
ExtractSslProtocol()还处理压缩
GzipStream或者
DeflateStream当被返回
WebRequest
AutomaticDecompression被激活。
验证将在中进行
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-urlenpred"; 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对所使用的安全协议的一些有用的信息。(请参阅:传输层安全性(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引用连接上下文句柄是不公开的。
因此,您只能再次通过反射或通过返回的
System.Net.Security.AuthenticatedStream派生类(
System.Net.Security.SslStream和
System.Net.Security.NegotiateStream)来获取它
TcpClient.GetStream()。
不幸的是,WebRequest / WebResponse返回的Stream不能转换为这些类。仅通过非公共属性和字段引用连接和流类型。
我正在发布汇编的文档,它可能会帮助您找到获取该Context Handle的另一条路径。
声明,结构,枚举器列表位于QueryContextAttributesW(PASTEBIN)中。
Microsoft TechNet
身份验证结构
MSDN
使用Schannel创建安全连接
获取有关Schannel连接的信息
查询Schannel上下文的属性
QueryContextAttributes(Schannel)
代码库(部分)
.NET参考源
Internals.cs
内部结构SSPIHandle
{}
内部枚举ContextAttribute
{}
更新1:
我在您对另一个答案的评论中看到,您
TcpClient()不接受使用该解决方案
。我还是把它留在这里,所以本·沃伊特(Ben
Voigt)在这本书中的评论
将对其他感兴趣的人有用。另外,3种可能的解决方案要优于2种。
在提供的上下文中,有关TcpClient()
SslStream用法的一些实现细节。
如果在初始化WebRequest之前需要协议信息,则可以使用TLS连接所需的相同工具在相同的上下文中建立TcpClient()连接。即,
ServicePointManager.SecurityProtocol定义支持的协议和
ServicePointManager.ServerCertificatevalidationCallback验证服务器证书。
TcpClient()和WebRequest都可以使用以下设置:
-启用所有协议,并让Tls握手确定将使用哪个协议。
-定义一个
RemoteCertificatevalidationCallback()委托来验证
X509Certificates服务器传递的
X509Chain。
实际上,建立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协议版本
-密码和Hash算法
- SSL握手中使用的服务器证书
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; }}


