web-dev-qa-db-ja.com

クライアント証明書がリクエストに追加されない(証明書の検証)

クライアント証明書を使用して、外部の本番サーバーに対して単純なGETリクエストを実行しようとしています。彼らは私たちの証明書をサーバーに追加し、私はPostman(ChromeアプリとWindowsネイティブアプリの両方)と標準のブラウザーの両方)を介して正常にリクエストを行いました: Postman showing status OK

ChromeアプリバージョンのPostmanは、Chromeの組み込みの証明書Finderを使用します。ネイティブのPostmanアプリには、.crtファイルと.keyファイルが必要です。これは私が作成したものです .p12ファイル

つまり、証明書はストアで正常に検出され、ファイルから使用した場合にも機能します(Windowsネイティブアプリでは、.NETで可能である必要があります)。


C#で証明書を取得する

私の単純なC#(。NET Framework 4.5.1)コンソールアプリケーションでは、ストア(またはファイル)から証明書を取得し、それを使用して ファイルの暗号化と復号化 (私はそれは私が私のアプリケーションからそれに完全にアクセスできることを意味します):

_private static X509Certificate2 GetCertificate(string thumbprint)
{
    X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
    X509Certificate2Collection coll =
        store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint,
            validOnly: true);
    X509Certificate2 certificate = coll.Count == 0 ? null : coll[0];
    return certificate;
}
_

アプリケーションコード

HttpClientまたはHttpWebRequestのいずれかを使用してサーバーにリクエストを送信します。

_//A global setting to enable TLS1.2 which is disabled in .NET 4.5.1 and 4.5.2 by default,
//and disable SSL3 which has been deprecated for a while.
//The server I'm connecting to uses TLS1.2
ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11
    | SecurityProtocolType.Tls12;

X509Certificate cert = GetCertificate(thumbprint);
string url = "https://sapxi.example.com/XISOAPAdapter/MessageServlet";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.ClientCertificates.Add(cert);
request.Method = WebRequestMethods.Http.Get;

WebResponse basicResponse = request.GetResponse(); //This is where the exception is thrown
string responseString = new StreamReader(basicResponse.GetResponseStream()).ReadToEnd();
_

例外

HttpClientまたはHttpWebRequestの両方が同じ例外をスローします。

(WebException)基になる接続が閉じられました:送信時に予期しないエラーが発生しました。

(IOException)トランスポート接続からデータを読み取れません:既存の接続がリモートホストによって強制的に閉じられました。

(SocketException)既存の接続がリモートホストによって強制的に閉じられました


VisualStudioでリクエストをトレースする

トレースを有効にする 、証明書と秘密鍵の両方が見つかった出力を取得します(詳細なメッセージを除外しました):

_System.Net Error: 0 : [29136] Can't retrieve proxy settings for Uri 'https://sapxi.example.com/XISOAPAdapter/MessageServlet'. Error code: 12180.
System.Net Information: 0 : [29136] Associating HttpWebRequest#21454193 with ServicePoint#60068066
System.Net Information: 0 : [29136] Associating Connection#3741682 with HttpWebRequest#21454193
System.Net.Sockets Information: 0 : [29136] Socket#33675143 - Created connection from 192.168.168.177:56114 to 131.165.*.*:443.
System.Net Information: 0 : [29136] Connection#3741682 - Created connection from 192.168.168.177:56114 to 131.165.*.*:443.
System.Net Information: 0 : [29136] TlsStream#43332040::.ctor(Host=sapxi.example.com, #certs=1)
System.Net Information: 0 : [29136] Associating HttpWebRequest#21454193 with ConnectStream#54444047
System.Net Information: 0 : [29136] HttpWebRequest#21454193 - Request: GET /XISOAPAdapter/MessageServlet HTTP/1.1

System.Net Information: 0 : [29136] ConnectStream#54444047 - Sending headers
{
Host: sapxi.example.com
Connection: Keep-Alive
}.
System.Net Information: 0 : [29136] SecureChannel#20234383::.ctor(hostname=sapxi.example.com, #clientCertificates=1, encryptionPolicy=RequireEncryption)
System.Net Information: 0 : [29136] Enumerating security packages:
System.Net Information: 0 : [29136]     Negotiate
System.Net Information: 0 : [29136]     NegoExtender
System.Net Information: 0 : [29136]     Kerberos
System.Net Information: 0 : [29136]     NTLM
System.Net Information: 0 : [29136]     TSSSP
System.Net Information: 0 : [29136]     pku2u
System.Net Information: 0 : [29136]     WDigest
System.Net Information: 0 : [29136]     Schannel
System.Net Information: 0 : [29136]     Microsoft Unified Security Protocol Provider
System.Net Information: 0 : [29136]     Default TLS SSP
System.Net Information: 0 : [29136]     CREDSSP
System.Net Information: 0 : [29136] SecureChannel#20234383 - Attempting to restart the session using the user-provided certificate:
*my certificate is here* (Issuer = CN=TRUST2408 OCES CA II, O=TRUST2408, C=DK)
System.Net Information: 0 : [29136] SecureChannel#20234383 - Left with 1 client certificates to choose from.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Trying to find a matching certificate in the certificate store.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Locating the private key for the certificate:
*my certificate is here*
System.Net Information: 0 : [29136] SecureChannel#20234383 - Certificate is of type X509Certificate2 and contains the private key.
System.Net Information: 0 : [29136] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent  = Outbound, scc     = System.Net.SecureCredential)
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = (null), targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffer length=0, Out-Buffer length=171, returned code=ContinueNeeded).
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=ContinueNeeded).
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=ContinueNeeded).
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=ContinueNeeded).
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CredentialsNeeded).
System.Net Information: 0 : [29136] SecureChannel#20234383 - We have user-provided certificates. The server has specified 8 issuer(s). Looking for certificates that match any of the issuers.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Selected certificate:
*my certificate is here*
System.Net Information: 0 : [29136] SecureChannel#20234383 - Left with 1 client certificates to choose from.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Trying to find a matching certificate in the certificate store.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Locating the private key for the certificate:
*my certificate is here*
System.Net Information: 0 : [29136] SecureChannel#20234383 - Certificate is of type X509Certificate2 and contains the private key.
System.Net Information: 0 : [29136] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent  = Outbound, scc     = System.Net.SecureCredential)
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=349, returned code=ContinueNeeded).
System.Net.Sockets Error: 0 : [29136] Socket#33675143::UpdateStatusAfterSocketError() - ConnectionReset
System.Net.Sockets Error: 0 : [29136] Exception in Socket#33675143::Receive - An existing connection was forcibly closed by the remote Host.
System.Net Error: 0 : [29136] Exception in HttpWebRequest#21454193:: - The underlying connection was closed: An unexpected error occurred on a send..
_

上記のセクションがもう一度繰り返され、最後に例外チェーンがスローされます。サーバーの実際のURLとIPを例に置き換えました。

からの行

ユーザー提供の証明書があります。サーバーは8つの発行者を指定しました。発行者のいずれかに一致する証明書を探しています。

証明書のタイプはX509Certificate2で​​、秘密鍵が含まれています。

証明書がHttpWebRequestsの内部動作で正しく検出されていると思わせます。

この出力から何がうまくいかないのかわかりません。


Wiresharkを使用したデバッグ

WiresharkでPostmanリクエストとC#コードを比較しましたが、唯一の違いは、_Client Verify_部分(証明書全体を含む)がC#から送信されるのではなく、Postman(およびブラウザー)経由で送信されることです。 。

Postmanとブラウザを介して、これはどのように見えるかです: Wireshark Postman successful request has "Certificate Verify"

そしてC#を介してこれはそれがどのように見えるかです: Wireshark C# unsuccessful request missing "Certificate Verify"

私には、私のアプリケーションがクライアント証明書を完全に無視しているように見えます。

また、クライアント証明書を提供しない場合(//request.ClientCertificates.Add(cert))、Wiresharkでもまったく同じ出力が得られます。これは、この疑いを裏付けているようです。 Visual Studioのトレース出力では、_Left with 0 client certificates to choose from._を取得するだけで、ストアなどで証明書を検索することはありません。

また、Wiresharkによって、PostmanがTLS1.2を正常に使用していること、および私のアプリケーションコードもTLS1.2を使用していることが明らかになっていることも注目に値します。


証明書が必要なローカルWebページの作成

これを機能させました 証明書を要求するようにIIS Expressを設定 そしてそれを呼び出します。ページで_Request.ClientCertificates_に証明書を見ることができますproperty。wiresharkでは、証明書検証を送信しないため、何かがまだ異なります。

しかし、このページは、IIS Expressがインストールを促した自己署名証明書を使用して、ローカルマシンで実行されます。有効な証明書を使用して、プロジェクトを運用サーバーにセットアップしていません。同じように動作するかどうかを確認します。

これが正確に何を意味するのかはわかりませんが、基本的なことを忘れていないこと、そしてこれがエッジケースか、C#のHttpWebRequestライブラリが持っていないプロトコルのいずれかであることを確認できると思います適切に処理しません。


他に何を試しましたか

_//Automatically verifying all server certificates (don't use this in production)
ServicePointManager.ServerCertificateValidationCallback =
    (sender, certificate, chain, sslPolicyErrors) =>
{
    return true; //This is not reached
};
ServicePointManager.Expect100Continue = true;
request.AllowAutoRedirect = true;
request.PreAuthenticate = true;
request.KeepAlive = false;
request.UserAgent = null;
request.CachePolicy = new HttpRequestCachePolicy(
    HttpCacheAgeControl.MaxAge, TimeSpan.FromSeconds(0));
//And several more that I didn't expect any effect from
_

それが役に立ったら、彼らのサーバーは実行されています SAP XI 、これは私がアクセスを拒否するアプリケーションです。その設定が他の設定と大きく異なるかどうかはわかりませんが、Postmanはリクエストを正常に実行できるため、大きく異なるとは思われません。最悪の場合、それはまだ標準に従っている平均以上のセキュリティプロトコルです。

私が持っている主なアイデアは、単純なASPページ/ API(クライアント証明書が必要)をセットアップして、本番サーバーに配置することです。別のアイデアは、HttpClient。さらに悪いことに、自分で作成して、Postmanが行っているトランザクションフローをコピーしてみてください。しかし、基本的にはアイデアが不足しています。助けていただければ幸いです。


質問

特定の質問を作成する必要がある場合は、次のようになります。

C#でTLS 1.2を使用して、クライアント証明書を使用してSAP XIサーバーにGETリクエストを送信するにはどうすればよいですか?


また、本番サーバーのURLまたはIPを公開できるかどうかもわかりません。確かに、サーバーに証明書を追加することはできないため、どちらの方法でも自分で接続することはできません。ですから、これは完全に再現できるわけではありません。サーバーがKMDに属していることを明らかにしても害はないと思います。

しかし、自分のページ/サービスに正常に接続して、そこでクライアント証明書を見ることができれば、どちらの方法でもゴールポストを通過すると思うので、それが進むべき道だと思います。

質問が長かったことをお詫びしますが、このようにして、非常に類似した問題を診断する回答者や将来の人々に役立つ多くの背景調査と詳細を提供しました。よくある問題のいくつかも質問に含めようとしました。

もちろん、答えが得られない場合は、理解したときに自分で答えます。

前もって感謝します。

7
Aske B.

ローカルでホストされているページからWiresharkにソケットデータをキャプチャする方法を調査しているときに、誤って偶然見つけました 「新しいバージョンのWindows」では「証明書の検証」がTLS 1.2経由で送信されないという記事 ( Windows 10のように)。

そこで、プロトコルをTLS 1.0に変更し、リクエストは次のように処理されました。

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;

TLS 1.1では、その記事の男が言ったこととは異なり、例外が発生します。

(WebException)要求は中止されました:SSL/TLSセキュアチャネルを作成できませんでした。

なぜこれが機能したのかは、この問題のデバッグのスケジュールがすでにかなり遅れているため、現在調査する時間はありませんが、バグのように聞こえます 別のユーザーが別の質問で主張

私は マイクロソフトの記事 これらの行に沿って次のように言っているのを見つけました:

この問題は、TLSセッションを不適切な方法でダウングレードするサーバーでのみ発生します(サーバーがサポートしていないTLSプロトコルバージョンを受信したときにTCPリセットを送信するなど))

しかし、私はTLS 1.2から始めており、サーバーはTLS 1.2を(PostmanとChromeを介して)明確に受け入れているので、同じ方法などで実装されていないTLS1.2プロトコルのごく一部である必要があります。ただし、PostmanネイティブWindowsアプリがどのようにTLS1.2を使用するのかはまだわかりません。

InternetExplorerが最初にTLS1.2を試行し、次に(私のクライアントのように)2回リセットした後、TLS1.0にダウングレードして通過することは注目に値するかもしれません。私には、これは記事で説明したInternetExplorerの更新と非常によく似ています。

Wireshark showing IE resetting TLS 1.2 connection with Certificate Verify, and then downgrading to TLS 1.0


これは(「理由」の詳細に関しては)良い答えではないことは理解していますが、少なくとも、同様の問題に遭遇した場合に何を試みるかについてのヒントを提供します。

誰かがこの問題を理解していて、おそらくTLS 1.2をサポートする方法さえ知っているなら、私はそれをとても感謝しています。

2
Aske B.

最近、.Net4.7.2を使用して同じ問題が発生しました。私の場合 cert.HasPrivateKeyはtrueを返しますが、cert.PrivateKeyはnullを返します。修正は、秘密鍵を使用して証明書をpfxとしてエクスポートし、それをメモリにロードして戻すことでした。

// Load the cert with private key
X509Certificate2 cert = ... 

// When the certificate is created, the private key is not associated with the object. In order
// for this to work correctly, we need to export it to a PFX and then re-import the cert.
byte[] certBytes = cert.Export(X509ContentType.Pfx);
cert = new X509Certificate2(certBytes);

この後、HttpClientは証明書をサーバーに正常に送信します。それが役に立てば幸い。

1
eNaught

私はそれを仲間に解決しました。 TLS1.2には何の問題もありません。必要なのは、HttpWebRequestクラスにrequest.UserAgent = "Take it from your broewser's request header";メンバーを設定することだけです。

これが問題の解決に役立つかどうかをお知らせください。