c# किस TLS संस्करण पर बातचीत हुई थी?




.net ssl (4)

नीचे दिया गया समाधान सबसे निश्चित रूप से एक "हैक" है जिसमें यह प्रतिबिंब का उपयोग करता है, लेकिन यह वर्तमान में अधिकांश स्थितियों को कवर करता है जो आप एक HttpWebRequest के साथ हो सकते हैं। यदि नल संस्करण निर्धारित नहीं किया जा सका तो यह अशक्त हो जाएगा। इससे पहले कि आप अनुरोध स्ट्रीम में कुछ भी लिखें, यह उसी अनुरोध में Tls संस्करण की पुष्टि करता है। यदि आप विधि कॉल करते समय स्ट्रीम Tls हैंडशेक अभी तक नहीं हुआ है, तो यह इसे ट्रिगर करेगा।

आपका नमूना उपयोग इस तरह दिखेगा:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("...");
request.Method = "POST";
if (requestPayload.Length > 0)
{
    using (Stream requestStream = request.GetRequestStream())
    {
        SslProtocols? protocol = GetSslProtocol(requestStream);
        requestStream.Write(requestPayload, 0, requestPayload.Length);
    }
}

और विधि:

public static SslProtocols? GetSslProtocol(Stream stream)
{
    if (stream == null)
        return null;

    if (typeof(SslStream).IsAssignableFrom(stream.GetType()))
    {
        var ssl = stream as SslStream;
        return ssl.SslProtocol;
    }

    var flags = BindingFlags.NonPublic | BindingFlags.Instance;

    if (stream.GetType().FullName == "System.Net.ConnectStream")
    {
        var connection = stream.GetType().GetProperty("Connection", flags).GetValue(stream);
        var netStream = connection.GetType().GetProperty("NetworkStream", flags).GetValue(connection) as Stream;
        return GetSslProtocol(netStream);
    }

    if (stream.GetType().FullName == "System.Net.TlsStream")
    {
        // type SslState
        var ssl = stream.GetType().GetField("m_Worker", flags).GetValue(stream);

        if (ssl.GetType().GetProperty("IsAuthenticated", flags).GetValue(ssl) as bool? != true)
        {
            // we're not authenticated yet. see: https://referencesource.microsoft.com/#System/net/System/Net/_TLSstream.cs,115
            var processAuthMethod = stream.GetType().GetMethod("ProcessAuthentication", flags);
            processAuthMethod.Invoke(stream, new object[] { null });
        }

        var protocol = ssl.GetType().GetProperty("SslProtocol", flags).GetValue(ssl) as SslProtocols?;
        return protocol;
    }

    return null;
}

मेरा ऐप .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);
    }
}

मुझे केवल लॉगिंग / डीबगिंग उद्देश्यों के लिए इस जानकारी की आवश्यकता है, इसलिए यह महत्वपूर्ण नहीं है कि अनुरोध स्ट्रीम में लिखने या प्रतिक्रिया प्राप्त करने से पहले मेरे पास यह जानकारी हो। मैं इस जानकारी के लिए नेट ट्रेसिंग लॉग को पार्स नहीं करना चाहता हूं, और मैं एक दूसरा कनेक्शन (SslStream या समान का उपयोग करके) भी नहीं बनाना चाहता।


जिस तरह से मैं समझ सकता हूं कि टेस्ट कनेक्शन बनाने के लिए SslStream का उपयोग करें, फिर SslProtocol संपत्ति की जांच करें।

TcpClient client = new TcpClient(decodedUri.DnsSafeHost, 443);
SslStream sslStream = new SslStream(client.GetStream());

// use this overload to ensure SslStream has the same scope of enabled protocol as HttpWebRequest
sslStream.AuthenticateAsClient(decodedUri.Host, null,
    (SslProtocols)ServicePointManager.SecurityProtocol, true);

// Check sslStream.SslProtocol here

client.Close();
sslStream.Close();

मैंने जाँच की है कि sslStream.SslProtocl हमेशा sslStream.SslProtocl होगा जो कि HttpWebRequest के Connection द्वारा उपयोग किया जाता है।


यहाँ और वहाँ कुछ विचारों को एक साथ रखते हुए, मैंने प्रत्येक प्रोटोकॉल के परीक्षण के लिए एक सरल विधि की, एक विशेष प्रकार के कनेक्शन को प्रत्येक प्रयास के लिए मजबूर किया। अंत में, मुझे उन परिणामों के साथ एक सूची मिलती है, जिनका उपयोग मेरी आवश्यकता है।

Ps: परीक्षण केवल तभी मान्य होता है जब आप जानते हैं कि वेबसाइट ऑनलाइन है - आप इसे जांचने के लिए पहले का परीक्षण कर सकते हैं।

    public static IEnumerable<T> GetValues<T>()
    {
        return Enum.GetValues(typeof(T)).Cast<T>();
    }

    private Dictionary<SecurityProtocolType, bool> ProcessProtocols(string address)
    {   
        var protocolResultList = new Dictionary<SecurityProtocolType, bool>();
        var defaultProtocol = ServicePointManager.SecurityProtocol;

        ServicePointManager.Expect100Continue = true;
        foreach (var protocol in GetValues<SecurityProtocolType>())
        {
            try
            {
                ServicePointManager.SecurityProtocol = protocol;

                var request = WebRequest.Create(address);
                var response = request.GetResponse();

                protocolResultList.Add(protocol, true);
            }
            catch
            {
                protocolResultList.Add(protocol, false);
            }
        }

        ServicePointManager.SecurityProtocol = defaultProtocol;

        return protocolResultList;
    }

आशा है इससे आपको मदद मिलेगी


TlsStream->SslState->SslProtocol प्रॉपर्टी की वैल्यू पाने के लिए आप रिफ्लेक्शन का इस्तेमाल कर सकते हैं।
यह जानकारी HttpWebRequest.GetRequestStream() और HttpWebRequest.GetResponseStream() दोनों द्वारा लौटाई गई स्ट्रीम से निकाली जा सकती है।

ExtractSslProtocol() WebRequest AutomaticDecompression के सक्रिय होने पर वापस WebRequest वाले संकुचित GzipStream या DeflateStream को भी संभालती है।

सत्यापन ServerCertificateValidationCallback में होगा, जिसे अनुरोध के साथ आरंभ होने पर कहा जाता है। ServerCertificateValidationCallback request.GetRequestStream()

नोट : SecurityProtocolType.Tls13 में .Net फ्रेमवर्क 4.8+ और .Net Core 3.0+

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

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:
QueryContextAttributesW() -> 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 संरचना का संदर्भ 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 जो संदर्भ संदर्भ TlsStream.Context.m_SecurityContext._handle है वह सार्वजनिक नहीं है।
इस प्रकार, आप इसे फिर से प्राप्त कर सकते हैं, केवल प्रतिबिंब के माध्यम से या System.Net.Security.AuthenticatedStream व्युत्पन्न वर्ग ( System.Net.Security.SslStream और System.Net.Security.NegotiateStream ) TcpClient.GetStream() वापस आ गए।

दुर्भाग्य से, WebRequest / WebResponse द्वारा दी गई स्ट्रीम को इन वर्गों में नहीं डाला जा सकता है। कनेक्शन और स्ट्रीम प्रकार केवल गैर-सार्वजनिक गुणों और फ़ील्ड के माध्यम से संदर्भित होते हैं।

मैं इकट्ठे प्रलेखन को प्रकाशित कर रहा हूं, यह शायद आपको उस संदर्भ को प्राप्त करने के लिए एक और रास्ता निकालने में मदद करेगा।

घोषणाएँ, संरचनाएँ, गणनाकर्ता सूचियाँ QueryContextAttributesW (PASTEBIN) में हैं

Microsoft TechNet
प्रमाणीकरण संरचनाएं

MSDN
Schannel का उपयोग करके एक सुरक्षित कनेक्शन बनाना

Schannel कनेक्शन के बारे में जानकारी प्राप्त करना

एक स्कैनेल संदर्भ के गुण को छोड़कर

QueryContextAttributes (Schannel)

कोड आधार (आंशिक)

.NET संदर्भ स्रोत

Internals.cs

आंतरिक संरचना SSPIHandle {}

आंतरिक enum प्रसंगअभियान {}

अद्यतन 1:

मैंने आपकी टिप्पणी में एक अन्य जवाब में देखा कि TcpClient() का उपयोग करने वाला समाधान आपके लिए स्वीकार्य नहीं है। मैं इसे वैसे भी यहाँ छोड़ रहा हूँ इसलिए इस में बेन वोइगट की टिप्पणी किसी और के लिए उपयोगी होगी। इसके अलावा, 3 संभावित समाधान 2 से बेहतर हैं।

प्रदान किए गए संदर्भ में TcpClient() SslStream उपयोग पर कुछ कार्यान्वयन विवरण।

यदि किसी WebRequest को प्रारंभ करने से पहले प्रोटोकॉल informations की आवश्यकता होती है, तो TcpClient () कनेक्शन को TLS कनेक्शन के लिए आवश्यक समान उपकरणों का उपयोग करके उसी संदर्भ में स्थापित किया जा सकता है। अर्थात्, ServicePointManager.SecurityProtocol समर्थित प्रोटोकॉल और ServicePointManager.SecurityProtocol को परिभाषित करने के लिए सर्वर प्रमाणपत्र को मान्य करता है।

TcpClient () और WebRequest दोनों इन सेटिंग्स का उपयोग कर सकते हैं:
- सभी प्रोटोकॉल को सक्षम करें और Tls हैंडशेक को निर्धारित करें कि किसका उपयोग किया जाएगा।
- एक RemoteCertificateValidationCallback() में सर्वर पास X509Certificates RemoteCertificateValidationCallback() को मान्य करने के लिए एक RemoteCertificateValidationCallback() प्रतिनिधि को परिभाषित करें।

व्यवहार में, TlspClient या WebRequest कनेक्शन स्थापित करते समय Tls हैंडशेक समान है।
यह दृष्टिकोण आपको यह बताता है कि आपके HttpWebRequest को Tls Protocol किस सर्वर के साथ बातचीत करेगा

SslStream प्राप्त करने और उसका मूल्यांकन करने के लिए एक TcpClient() सेटअप TcpClient()
checkCertificateRevocation ध्वज को false सेट किया गया false , इसलिए प्रक्रिया निरस्तीकरण सूची देखने में समय बर्बाद नहीं करेगी।
प्रमाणपत्र सत्यापन कॉलबैक ServicePointManager में निर्दिष्ट है

TlsInfo TLSInfo;
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 वर्ग स्थापित सुरक्षित कनेक्शन पर कुछ जानकारी एकत्र करता है:
- टीआरएस प्रोटोकॉल संस्करण
- सिफर और हैश एल्गोरिदम
- 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; }
}