谈判了哪个TLS版本?

Fre*_*ric 23 .net c# ssl

我的应用程序在.NET 4.7中运行.默认情况下,它将尝试使用TLS1.2.是否有可能知道在执行时协商了哪个TLS版本,例如,如下所示的HTTP请求?

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(decodedUri);
if (requestPayload.Length > 0)
{
    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(requestPayload, 0, requestPayload.Length);
    }
}
Run Code Online (Sandbox Code Playgroud)

我只需要这些信息用于记录/调试,因此在写入请求流或接收响应之前获取此信息并不重要.我不希望解析此信息的网络跟踪日志,我也不想创建第二个连接(使用SslStream或类似的连接).

Jim*_*imi 14

您可以使用Reflection来获取TlsStream->SslState->SslProtocol属性值.
这些信息可以从双方返回的流中提取HttpWebRequest.GetRequestStream()HttpWebRequest.GetResponseStream().

更新:
ExtractSslProtocol()方法现在检测压缩GzipStreamDeflateStream激活WebRequest AutomaticDecompression时可能返回的方法 .

验证将发生在ServerCertificateValidationCallback,当使用初始化请求时调用request.GetRequestStream()

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);
}
Run Code Online (Sandbox Code Playgroud)

SecurityProtocolType.Tls13对所使用的安全协议的一些有用的信息.(请参阅:传输层安全性(TLS)参数(IANA)RFC 5246).
所使用的安全协议类型可以提供足够的信息,因为每个协议版本都支持哈希和加密算法的子集.
Tls 1.2,介绍4.8+和弃用3.0+以及RemoteCertificateValidationCallback密码(所有变体都列在链接文档中).

在这里,我插入了一个HMAC-SHA256,列出了正在使用的算法.
请注意,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;
}
Run Code Online (Sandbox Code Playgroud)


更新2:
IDEA- > DES方法,允许查询一个初始化流的连接安全性上下文.

[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);
Run Code Online (Sandbox Code Playgroud)

从文档中可以看出,此方法返回OIDExtractor引用secur32.dll结构的方法:

//[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;
}
Run Code Online (Sandbox Code Playgroud)

QueryContextAttributesW()成员是SslProtocol.

有什么问题.
void* buffer引用连接上下文句柄是不公开的.
因此,您只能通过反射或通过返回的SecPkgContext_ConnectionInfo派生类(SchProtocols dwProtocolTlsStream.Context.m_SecurityContext._handle)来获取它System.Net.Security.AuthenticatedStream.

遗憾的是,WebRequest/WebResponse返回的Stream无法强制转换为这些类.Connections和Streams Types仅通过非公共属性和字段引用.

我正在发布已组装的文档,它可能会帮助您找到另一条获取Context Handle的途径.

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

Microsoft TechNet
身份验证结构

MSDN
使用Schannel创建安全连接

获取有关Schannel连接的信息

查询Schannel上下文的属性

QueryContextAttributes(Schannel)

代码库(部分)

.NET参考源

Internals.cs

内部结构SSPIHandle {}

内部枚举ContextAttribute {}


更新1:

我在你的评论中看到另一个答案,使用的解决方案 System.Net.Security.SslStream对你来说是不可接受的.无论如何,我将它留在这里,所以Ben Voigt在这篇文章中的评论对任何有兴趣的人都有 用.此外,3种可能的解决方案优于2种.

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

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

TcpClient()和WebRequest都可以使用以下设置:
- 启用所有协议,让Tls Handshake确定将使用哪个协议.
- 定义一个TcpClient()委托来验证ServicePointManager.SecurityProtocol服务器传入的代理ServicePointManager.ServerCertificateValidationCallback.

实际上,Tls Handshake在建立TcpClient或WebRequest连接时是相同的.
这种方法可以让您知道您的HttpWebRequest 与同一服务器协商的Tls协议.

设置a RemoteCertificateValidationCallback()以接收和评估X509Certificates.
X509Chain标志设置为TcpClient(),因此该过程不会浪费时间查找撤销列表.
证书验证Callback与中指定的相同SslStream

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);

//(...)
Run Code Online (Sandbox Code Playgroud)

checkCertificateRevocation级收集建立安全连接的一些信息:
- 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; }
}
Run Code Online (Sandbox Code Playgroud)