我的应用程序在.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属性值。
可以从HttpWebRequest.GetRequestStream()HttpWebRequest.GetResponseStream()返回的Stream中提取此信息。
ExtractSslProtocol()还处理GzipStream AutomaticDecompression激活时返回的压缩的DeflateStreamWebRequest

验证将在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;

//(...)
// 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并弃用了IDEADES密码(所有变体在链接的文档中列出)。

在这里,我插入了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不是公共(public)的。
因此,您只能再次通过反射或通过System.Net.Security.AuthenticatedStream返回的System.Net.Security.SslStream派生类(System.Net.Security.NegotiateStreamTcpClient.GetStream())来获取它。

不幸的是,WebRequest/WebResponse返回的Stream不能转换为这些类。仅通过非公共(public)属性和字段引用连接和流类型。

我正在发布汇编的文档,它可能会帮助您找到获取该Context Handle的另一条路径。

声明,结构,枚举器列表位于QueryContextAttributesW (PASTEBIN)中。

Microsoft TechNet
Authentication Structures

MSDN
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:



在提供的上下文中有关TcpClient() SslStream用法的一些实现细节。

如果在初始化WebRequest之前需要协议(protocol)信息,则可以使用TLS连接所需的相同工具在相同的上下文中建立TcpClient()连接。即, ServicePointManager.SecurityProtocol 定义支持的协议(protocol), ServicePointManager.ServerCertificateValidationCallback 验证服务器证书。

TcpClient()和WebRequest都可以使用以下设置:
-启用所有协议(protocol),并让TLS握手确定将使用哪个协议(protocol)。
-定义RemoteCertificateValidationCallback()委托(delegate)以验证服务器在X509Certificates中传递的X509Chain

实际上,建立TcpClient或WebRequest连接时,TLS握手是相同的。
这种方法使您知道HttpWebRequest 将使用哪种Tls协议(protocol)与同一服务器协商。

设置一个TcpClient()来接收和评估SslStreamcheckCertificateRevocation标志设置为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协议(protocol)版本
-密码和哈希算法
-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; }
}

关于c# - 协商了哪个TLS版本?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48589590/

10-09 19:52
查看更多