どのTLSバージョンがネゴシエートされましたか? 質問する

どのTLSバージョンがネゴシエートされましたか? 質問する

私のアプリは .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);
    }
}

この情報はログ記録/デバッグの目的でのみ必要なので、リクエスト ストリームに書き込む前や応答を受信する前にこの情報を取得することは重要ではありません。この情報のためにネット トレース ログを解析したり、2 番目の接続 (SslStream などを使用) を作成したりすることは望んでいません。

ベストアンサー1

Reflection を使用してプロパティ値を取得できますTlsStream->SslState->SslProtocol。この情報は、との
両方によって返される Stream から抽出できます。HttpWebRequest.GetRequestStream()HttpWebRequest.GetResponseStream()

ExtractSslProtocol()、圧縮されたGzipStreamまたはDeflateStream返されるファイルも処理します。WebRequest 自動解凍有効になります。

検証は で行われServerCertificateValidationCallback、リクエストが初期化されたときに呼び出されます。request.GetRequestStream()

注記:SecurityProtocolType.Tls13.Net Framework4.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使用されているセキュリティ プロトコルに関する役立つ情報が記載されています。(参照:トランスポート層セキュリティ (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

    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 です。

落とし穴は何でしょうか。接続コンテキスト ハンドルを参照する は public ではありません。
したがって、これもまた、リフレクションまたは によって返される派生クラス (および)を通じてのみ取得できます。TlsStream.Context.m_SecurityContext._handle
System.Net.Security.AuthenticatedStreamSystem.Net.Security.SslStreamSystem.Net.Security.NegotiateStreamTcpClient.GetStream()

残念ながら、WebRequest/WebResponse によって返されるストリームはこれらのクラスにキャストできません。接続およびストリーム タイプは、非パブリックのプロパティとフィールドを通じてのみ参照されます。

まとめたドキュメントを公開しています。このドキュメントは、コンテキスト ハンドルに到達するための別のパスを見つけるのに役立つかもしれません。

宣言、構造体、列挙子リストはQueryContextAttributesW (貼り付け)

マイクロソフト テックネット
認証構造

マイクロソフト
Schannel を使用した安全な接続の作成

Schannel接続に関する情報の取得

Schannel コンテキストの属性のクエリ

クエリコンテキスト属性 (Schannel)

コードベース(一部)

.NET リファレンス ソース

内部.cs

内部構造体 SSPIHandle { }

内部列挙型 ContextAttribute { }


更新1:

別の回答へのコメントで、解決策がTcpClient()受け入れられないとわかりました。とにかくここに残しておきますので、ベン・ボイトこれは興味のある人にとって役立つでしょう。また、解決策が 2 つより 3 つあるほうが良いです。

実装の詳細については、TCPクライアント() SSLストリーム提供されたコンテキストでの使用。

WebRequestを初期化する前にプロトコル情報が必要な場合は、TLS接続に必要な同じツールを使用して、同じコンテキストでTcpClient()接続を確立できます。つまり、ServicePointManager.SecurityProtocolサポートされているプロトコルとServicePointManager.ServerCertificateValidationCallbackサーバー証明書を検証します。

TcpClient() と WebRequest はどちらも次の設定を使用できます。

  • すべてのプロトコルを有効にし、TLS ハンドシェイクによってどのプロトコルが使用されるかを決定します。
  • サーバーが渡すものRemoteCertificateValidationCallback()を検証するためのデリゲートを定義します。X509CertificatesX509Chain

実際には、TLSハンドシェイクはTcpClientまたはWebRequest接続を確立するときに同じです。
このアプローチにより、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; }
}

おすすめ記事