.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などを使用)。
以下のソリューションは、リフレクションを使用するという点で「ハック」と言えますが、現在はHttpWebRequestで発生する可能性のあるほとんどの状況をカバーしています。 Tlsバージョンを判別できなかった場合は、nullを返します。また、リクエストストリームに何かを書き込む前に、同じリクエスト内の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;
}
いくつかのアイデアをあちこちにまとめて、使用可能な各プロトコルをテストする簡単な方法を実行し、試行ごとに特定の種類の接続を強制しました。最後に、必要に応じて使用する結果のリストを取得します。
追伸:このテストは、Webサイトがオンラインであることがわかっている場合にのみ有効です。これを確認するために以前にテストを行うことができます。
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;
}
これが役に立つことを願って
私が理解できる唯一の方法は、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
がHttpWebRequest
のConnection
で使用されるTlsStream.m_worker.SslProtocol
と常に同じであることを確認しました。