2011年10月31日 星期一

使用 SecureSocket 連接 HTTPS

為了研究 FP11 的 SecureSocket 功能,作了以下的測試。

首先,有些官方文件要先看一下:

Sockets
http://help.adobe.com/en_US/as3/dev/WSb2ba3b1aad8a27b0-181c51321220efd9d1c-8000.html#WS5b3ccc516d4fbf351e63e3d118a9b90204-7cfb

這份文件中會提到幾個當使用 SecureSocket 連線 Server 時,遇到認證失敗的可能原因:

The CertificateStatus class defines string constants that represent the possible validation results:

    Expired—the certificate expiration date has passed.

    Invalid—there are a number of reasons that a certificate can be invalid. For example, the certificate could have been altered, corrupted, or it could be the wrong type of certificate.

    Invalid chain—one or more of the certificates in the server’s chain of certificates are invalid.

    Principal mismatch—the host name of the server and the certificate common name do not match. In other words, the server is using the wrong certificate.

    Revoked—the issuing certificate authority has revoked the certificate.

    Trusted—the certificate is valid and trusted. A SecureSocket object can only connect to a server that uses a valid, trusted certificate.

    Unknown—the SecureSocket object has not validated the certificate yet. The serverCertificateStatus property has this status value before you call connect() and before either a connect or an ioError event is dispatched.

    Untrusted signers—the certificate does not “chain” to a trusted root certificate in the trust store of the client computer.

第二個要知道的文件是:

SecureSocket.addBinaryChainBuildingCertificate()
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/SecureSocket.html#addBinaryChainBuildingCertificate%28%29

這裡提到一句話:

The certificate you add with this API must be a DER-encoded X.509 certificate. If the trusted parameter is true, the certificate you add with this API is considered a trusted anchor. 

看到這裡,可能會想著要自己製作 SSL X.509 認證檔,於是查到以下文章:

如何製作 SSL X.509 憑證?
http://www.imacat.idv.tw/tech/sslcerts.html.zh-tw

不過,其實接下來我暫時用不上這篇文章~



若要測試使用 SecureSocket 連接 socket server 的話,表示還要自己準備 server 端的開發,為了先簡化問題,所以我先嘗試連接 HTTPS web server 的作法,畢竟對我來說在 MS IIS 上設定 SSL 的難度,要比自己抄範例寫一個 .NET(或 Java) socket server 要簡單得多。

然而,認證的部分,我不會真的花大錢去買認證,所以可以直接參考 IIS 如何製作開發用的 Self-Signed SSL Certificate,我開發環境使用 IIS 6,所以可以參考以下文章:

How To: Create a Self-signed SSL Certificate for II6 (Windows Server 2003)
http://community.aspnix.com/windows-manuals-tutorials-tos/2669-create-self-signed-secure-socket-layer-certificate-ii6-windows-server-2003-a.html

如果使用 IIS 7 的話,可以查到更簡單的作法。


Web Server 端,另外會準備一個 hello world 的 html 頁面,晚點我會設定此頁面是否強制採用 SSL。


最後一篇參考文章,是說明如何在 AS3 使用基本的 Socket 類別去讀取 web server 80 port 的網頁:

ActionScript 3: Socket Programming
http://hejp.co.uk/flash/actionscript-3-socket-programming/

因此,以下的範例,將只需要拿此範例來改改要使用的 Socket 類別,以及要連線的位置、port 等等即可。




以上,是參考文章的部分,接下來開始 Server 的準備,假設你已經依照參考文章將 IIS 上某個 web site 設定好可以使用 SSL,我進行 self ssl 時所使用的 command 為 selfssl /N:CN=inner.FlashTeam.com /K:2048 /V:365 /S:1795414334 /T)。

然後準備一個 hello world 網頁,看一下在不同情況下的畫面:



狀況一,使用 http 連接 web site 的 http port,這裡我不是使用 80 port,而是改用自訂 43000 port;測試結果屬一般瀏覽網頁,正常!




狀況二,於 IIS 將 index.htm 網頁設定為必須使用 SSL 瀏覽,此時若是 client 端仍使用 http / 43000 port 連入的話,就會看到錯誤訊息:




狀況三,因此,client 改用 https / 43443 port 連入,便可以看到出現 hello world 的正常網頁了:





web server 準備好之後,準備來開發 flash 的部分:

TestSecureSocket.as
package
{
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.events.IOErrorEvent;
 import flash.events.ProgressEvent;
 import flash.events.SecurityErrorEvent;
 import flash.net.SecureSocket;
 import flash.net.Socket;
 import flash.system.Security;
 
 public class TestSecureSocket extends Sprite
 {
  private const TEST:int = 5;
  
  private var socket:Socket;

  public function TestSecureSocket()
  {
   trace(SecureSocket.isSupported)
   
   Security.allowDomain("*");
   
   var sslsocket:SecureSocket;
   
   if(TEST == 1){
    socket = new Socket();
    socket.connect("inner.flashteam.com", 43000);
    
   }else if(TEST == 2){
    sslsocket = new SecureSocket();
    sslsocket.addBinaryChainBuildingCertificate(new Key(), true);
    socket = sslsocket;
    socket.connect("inner.flashteam.com", 43000);
    
   }else if(TEST == 3){
    socket = new Socket();
    socket.connect("inner.flashteam.com", 43443);
    
   }else if(TEST == 4){
    sslsocket = new SecureSocket();
    sslsocket.addBinaryChainBuildingCertificate(new Key(), true);
    socket = sslsocket;
    socket.connect("inner.flashteam.com", 43443);
    
   }else if(TEST == 5){
    sslsocket = new SecureSocket();
    //sslsocket.addBinaryChainBuildingCertificate(new Key(), true);
    socket = sslsocket;
    socket.connect("inner.flashteam.com", 43443);
    
   }
   
   socket.addEventListener(Event.CONNECT, onConnect);
   socket.addEventListener(Event.CLOSE, onClose);
   socket.addEventListener(IOErrorEvent.IO_ERROR, onError);
   socket.addEventListener(ProgressEvent.SOCKET_DATA, onResponse);
   socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecError);
  }
  
  private function onConnect(e:Event):void {
   socket.writeUTFBytes("GET / HTTP/1.1\n");
   socket.writeUTFBytes("Host: inner.flashteam.com\n");
   socket.writeUTFBytes("\n");
  }
  
  private function onClose(e:Event):void {
   // Security error is thrown if this line is excluded
   socket.close();
  }
  
  private function onError(e:IOErrorEvent):void {
   trace("IO Error: "+e);
   
   if(socket is SecureSocket){
    var serverCertificateStatus:String = (socket as SecureSocket).serverCertificateStatus;
    trace("serverCertificateStatus=" + serverCertificateStatus);
   }
  }
  
  private function onSecError(e:SecurityErrorEvent):void {
   trace("Security Error: "+e);
  }
  
  private function onResponse(e:ProgressEvent):void {
   if (socket.bytesAvailable>0) {
    trace(socket.readUTFBytes(socket.bytesAvailable));
   }
  }
 }
}


Key.as
package
{
 import flash.utils.ByteArray;
 
 [Embed(source="key-x509.cer", mimeType="application/octet-stream")]
 public class Key extends ByteArray
 {
 }
}

上述範例程式碼中,我使用了 5 種測試情況,以下是各種情況的輸出訊息:


情況一,html page 不需使用 SSL,flash 使用 Socket 連 HTTP:正常

true
HTTP/1.1 200 OK

Content-Length: 107

Content-Type: text/html

Content-Location: http://inner.flashteam.com/index.htm

Last-Modified: Fri, 08 Oct 2010 07:45:32 GMT

Accept-Ranges: bytes

ETag: "3f63cc9bc66cb1:5897"

Server: Microsoft-IIS/6.0

X-Powered-By: ASP.NET

Date: Mon, 31 Oct 2011 05:44:50 GMT


<html>

 <head>

  <title>Hello World</title>

 </head>

 <body>

  <H1>Hello World</H1>

 </body>

</html>
[SWF] E:\works_test\TestSecureSocket\bin-debug\TestSecureSocket.swf - 3,553 bytes after decompression



情況二,html page 不需使用 SSL,flash 使用 SecureSocket 連 HTTP:得到 serverCertificateStatus=invalid

true
IO Error: [IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2031: 通訊端錯誤。 URL: inner.flashteam.com"]
serverCertificateStatus=invalid
[SWF] E:\works_test\TestSecureSocket\bin-debug\TestSecureSocket.swf - 3,550 bytes after decompression




情況三,html page 需使用 SSL,flash 使用 Socket 連 HTTPS:沒回傳

true
[SWF] E:\works_test\TestSecureSocket\bin-debug\TestSecureSocket.swf - 3,591 bytes after decompression





情況四,html page 需使用 SSL,flash 使用 SecureSocket 連 HTTPS:正常

true
HTTP/1.1 200 OK

Content-Length: 107

Content-Type: text/html

Content-Location: https://inner.flashteam.com/index.htm

Last-Modified: Fri, 08 Oct 2010 07:45:32 GMT

Accept-Ranges: bytes

ETag: "3f63cc9bc66cb1:5897"

Server: Microsoft-IIS/6.0

X-Powered-By: ASP.NET

Date: Mon, 31 Oct 2011 05:50:05 GMT



<html>

 <head>

  <title>Hello World</title>

 </head>

 <body>

  <H1>Hello World</H1>

 </body>

</html>
[SWF] E:\works_test\TestSecureSocket\bin-debug\TestSecureSocket.swf - 3,648 bytes after decompression





情況五,html page 需使用 SSL,flash 使用 SecureSocket 連 HTTPS,沒有提供 X.509 certificate:得到 serverCertificateStatus=untrustedSigners

true
IO Error: [IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2031: 通訊端錯誤。 URL: inner.flashteam.com"]
serverCertificateStatus=untrustedSigners
[SWF] E:\works_test\TestSecureSocket\bin-debug\TestSecureSocket.swf - 3,692 bytes after decompression




上述 狀況四 與 狀況五 的差別在於 flash 端是否有使用 X.509 certificate,下一問題就在於如何自己產生 *.cer 檔,也就是我上面 Key.as 程式中所 embed 的 key-x509.cer 從何而來?

我這裡的作法是,在 IIS 中設定過可使用 SSL 的 web site 中,可以找到 server 憑證,然後就可以匯出不包含 private key 只有 public key 的憑證檔了,而且還正好就是我們所需要的 DER encoded binary X.509 (.CER) 檔。






下一步,要研究的就是如何在自己開發的 .NET(或 Java) socket server 上,使用 SSL、提供憑證給 flash client、整合測試~

1 則留言:

Ben Chang 提到...

注記幾點事情:

要認識 SecureSocket,必須於 adobe 官網下載 FP11 用的 globalplayer.swc。

編譯時,設定的 target-player=11,另外要設定 swf-version=13。

為了要認識 swf-version 這個屬性,flex sdk 需要 4.5 以上。

執行環境中,若 loader.swf 載入 game.swf 的話,不僅 game.swf 要遵從以上要點之外,loader.swf 也必須重新編譯輸出成 swf-version=13 的版本才行。不然在 game.swf 中會得到 SecureSocket.isSupported = false 的情況。