次のコードを使用して、ハンドヘルド デバイス (Windows CE/コンパクト フレームワーク) から REST メソッドを呼び出そうとしています。
public static HttpWebRequest SendHTTPRequestNoCredentials(string uri, HttpMethods method, string data, string contentType)
{
ExceptionLoggingService.Instance.WriteLog("Reached
fileXferREST.SendHTTPRequestNoCredentials");
WebRequest request = null;
try
{
request = WebRequest.Create(uri);
request.Method = Enum.ToObject(typeof(HttpMethods), method).ToString();
request.ContentType = contentType;
((HttpWebRequest)request).Accept = contentType;
((HttpWebRequest)request).KeepAlive = false;
((HttpWebRequest)request).ProtocolVersion = HttpVersion.Version10;
if (method != HttpMethods.GET && method != HttpMethods.DELETE)
{
byte[] arrData = Encoding.UTF8.GetBytes(data);
request.ContentLength = arrData.Length;
using (Stream oS = request.GetRequestStream())
{
oS.Write(arrData, 0, arrData.Length);
}
}
else
{
request.ContentLength = 0;
}
}
catch (Exception ex)
{
String msgInnerExAndStackTrace = String.Format(
"{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException,
ex.StackTrace);
ExceptionLoggingService.Instance.WriteLog(String.Format("From
FileXferREST.SendHTTPRequestNoCredentials(): {0}", msgInnerExAndStackTrace));
}
return request as HttpWebRequest;
}
メソッドに渡される値は次のとおりです。
uri: "https://seastore.nrbq.ad/ggr.web/api/inventory/sendXML/duckbill/platypus/INV_3_20090313214959000.xml"
HttpMethods: HttpMethods.POST
data: [ some xml ]
contentType: "application/xml"
...しかし、「SSL/TLS の安全なチャネルを確立できませんでした ...System.Net.Sockets.SocketException: 既存の接続がリモート ホストによって強制的に閉じられました」ため、接続を確立できません。
では、SSL/TLS 用の安全なチャネルを確立して、既存の接続が感情的に離れたホストによって乱暴に閉じられないようにするにはどうすればよいでしょうか?
注意: WebException をキャッチしていたときに、このコードが原因でアプリがクラッシュしていたのは少しおかしなことだと思いますが、catch ブロックを一般的な例外に変更すると、サイレント モードで接続の試行が失敗しました (私ができる唯一の方法は、ログファイルを見て、問題があったことを伝えます)。
より具体的には、次のように HttpWebRequest SendHTTPRequestNoCredentials() の catch ブロックの WebException コードを使用します。
catch (WebException webex)
{
HttpWebResponse hwr = (HttpWebResponse)webex.Response;
HttpStatusCode hsc = hwr.StatusCode;
String webExMsgAndStatusCode = String.Format("{0} Status code == {1}", webex.Message,
hsc.ToString());
ExceptionLoggingService.Instance.WriteLog(String.Format("From
FileXferREST.SendHTTPRequestNoCredentials: {0}", webExMsgAndStatusCode));
}
...アプリがクラッシュし、ログ ファイルにこれらの事後分析メモ (恐ろしい NRE!) が記録されました。
Date: 3/13/2009 11:40:15 PM
Message: Reached FileXferREST.SendHTTPRequestNoCredentials
Date: 3/13/2009 11:40:31 PM
Message: From frmMain.SendInventories: NullReferenceException; Inner Ex: ; Stack Trace: at
HHS.FileXferREST.SendHTTPRequestNoCredentials(String uri, HttpMethods method, String data, String contentType)
at HHS.FileXferREST.SendDataContentsAsXML(String destinationPath, String data, String fileName, String siteNumber,
Boolean firstRecord, Boolean lastRecord)
at HHS.frmMain.SendInventories()
at HHS.frmMain.menuItemSEND_Inventories_Click(Object sender, EventArgs e)
. . .
ただし、catch ブロック内の一般的な例外コード (この投稿の上部に示されているように) を使用すると、アプリは晴れた夏の日曜日の朝に公園を散歩しているように見えました。例外メッセージもクラッシュも、冬の不満の兆候もありません。とにかく -- しかしログファイルはこれを明らかにします:
Date: 3/13/2009 11:54:52 PM
Message: Reached FileXferREST.SendHTTPRequestNoCredentials
Date: 3/13/2009 11:54:54 PM
Message: From FileXferREST.SendHTTPRequestNoCredentials(): Could not establish secure channel for SSL/TLS; Inner Ex:
System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.Socket.ReceiveNoCheck(Byte[] buffer, Int32 index, Int32 request, SocketFlags socketFlags)
at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
at System.Net.Connection.System.Net.ISslDataTransport.Receive(Byte[] buffer, Int32 offset, Int32 size)
at System.Net.SslConnectionState.ClientSideHandshake()
at System.Net.SslConnectionState.PerformClientHandShake()
at System.Net.Connection.connect(Object ignored)
at System.Threading.ThreadPool.WorkItem.doWork(Object o)
at System.Threading.Timer.ring()
; Stack Trace: at System.Net.HttpWebRequest.finishGetRequestStream()
at System.Net.HttpWebRequest.GetRequestStream()
at HHS.FileXferREST.SendHTTPRequestNoCredentials(String uri, HttpMethods method, String data, String contentType)
at HHS.FileXferREST.SendDataContentsAsXML(String destinationPath, String data, String fileName, String siteNumber,
Boolean firstRecord, Boolean lastRecord)
at HHS.frmMain.SendInventories()
at HHS.frmMain.menuItemSEND_Inventories_Click(Object sender, EventArgs e)
. . .
最後の興味深い情報にもかかわらず、本当に重要なのは、ハンドヘルド デバイスから SSL/TLS 用の安全なチャネルを確立するにはどうすればよいかということです。
アップデート
PC で実行されている「サンドボックス」アプリからコードを呼び出したところ、同一ではありませんが同様の例外が発生しました。これはそれがキャッチしたものです:
Message: From SendHTTPRequestNoCredentials(): The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.; Inner Ex: System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, Exception exception)
at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
. . .
更新 2
ここのコメントのいくつかとそれに付随するリンクに基づいて、コード内にこれを追加する必要があると考えていました。
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
...ある文脈では:
public static HttpWebRequest SendHTTPRequestNoCredentials(string uri, HttpMethods method, string data, string
contentType) { ServicePointManager.ServerCertificateValidationCallback += (送信者、証明書、チェーン、sslPolicyErrors) => true; WebRequest リクエスト = null; { request = WebRequest.Create(uri); を試してください。
...しかし、これは .NET 3.5 クライアント アプリですが、これによると [ http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback
(v=vs.90).aspx]、ServerCertificateValidationCallback はおそらく 3.5 で利用可能です。「ServerCertificateValidationCallback」は利用できません (「シンボルを解決できません」というメッセージが表示されます)。これは System.Net アセンブリにあるようですが、[.NET] タブの [参照の追加] から利用できるアセンブリがないため、System.Net への参照をプロジェクトに追加しようとしても無駄です。アルファベット順のリストは、「System.Messaging」から「System.Net.Irda」までです。
この不足は、これが機能の乏しい Compact Framework プロジェクトであるためだと思います。
これがそうであると仮定すると (Compact Framework には ServerCertificateValidationCallback が含まれていません)、このシナリオの回避策は何ですか? クライアント ハンドヘルド アプリがサーバー (ローカル ネットワークで実行されている REST アプリ) 上の自己署名 SSL 証明書を受け入れるようにするにはどうすればよいですか?
更新 3
[コントロール パネル] > [プログラム] > [Windows の機能の有効化または無効化] > [インターネット インフォメーション サービス] > [World Wide Web サービス] > [セキュリティ] で、次のいずれかまたは両方をチェックまたはチェックする必要があります。
Client Certificate Mapping Authentication
IIS Client Certificate Mapping Authentication
?
更新 4
次のように ServicePoint にアクセスできます。
ServicePoint svcPoint = ServicePointManager.FindServicePoint(uri);
...しかし、これは私に何の役にも立たないでしょうか。証明書を常に受け入れるのと同等のものに設定できますか? IOW、ここで何が必要ですか:
ServicePoint svcPoint = ServicePointManager.FindServicePoint(uri);
svcPoint.Certificate = ???
更新 5
このコードでも:
namespace HHS
{
using System.Net;
using System.Security.Cryptography.X509Certificates;
class TrustAllCertificatesPolicy : ICertificatePolicy
{
public TrustAllCertificatesPolicy()
{
}
public bool CheckValidationResult(ServicePoint sp, X509Certificate cert, WebRequest req, int problem)
{
return true;
}
}
}
private void frmMain_Load(object sender, EventArgs e)
{
System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatesPolicy();
}
...私はまだこれを取得します:
Message: Reached FileXferREST.SendHTTPRequestNoCredentials
Date: 3/18/2009 11:41:09 PM
Message: From FileXferREST.SendHTTPRequestNoCredentials(): Could not establish secure channel for SSL/TLS; Inner Ex: System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.Socket.ReceiveNoCheck(Byte[] buffer, Int32 index, Int32 request, SocketFlags socketFlags)
at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
at System.Net.Connection.System.Net.ISslDataTransport.Receive(Byte[] buffer, Int32 offset, Int32 size)
at System.Net.SslConnectionState.ClientSideHandshake()
at System.Net.SslConnectionState.PerformClientHandShake()
at System.Net.Connection.connect(Object ignored)
at System.Threading.ThreadPool.WorkItem.doWork(Object o)
at System.Threading.Timer.ring()
; Stack Trace: at System.Net.HttpWebRequest.finishGetRequestStream()
at System.Net.HttpWebRequest.GetRequestStream()
at HHS.FileXferREST.SendHTTPRequestNoCredentials(String uri, HttpMethods method, String data, String contentType)
. . .
ところで、TrustAllCertificatesPolicy の (空の) コンストラクターはグレー表示されているため、おそらく意味がありません。