2011年11月14日 星期一

使用 SecureSocket 連接 Socket Server - Part 2

繼上一篇 使用 SecureSocket 連接 Socket Server 後,實際放到網路環境測試時,這才發現到,透過 SecureSocket 向 Socket Server 索取 policy file 時,也會經過編碼,所以 Socket Server 必須做些調整以支援此機制。

首先,先提供 flash 原始碼如下,與先前文章中的差別在於,分別使用了 open source 的 com.hurlant.crypto.tls.TLSSocket 與 SecureSocket 來作測試。

TestSecureSocket.as
package
{
 import com.hurlant.crypto.tls.SSLSecurityParameters;
 import com.hurlant.crypto.tls.TLSConfig;
 import com.hurlant.crypto.tls.TLSEngine;
 import com.hurlant.crypto.tls.TLSSocket;
 
 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 var socket:Socket;

  public function TestSecureSocket()
  {
   trace(SecureSocket.isSupported);
   
   Security.allowDomain("*");
   
   if(false){
    var secureSocket:SecureSocket = new SecureSocket();
    socket = secureSocket;
    
   }else{
    var tlsConfig:TLSConfig = new TLSConfig(TLSEngine.CLIENT, null, null, null, null, null, SSLSecurityParameters.PROTOCOL_VERSION);
    tlsConfig.trustAllCertificates = true;
    tlsConfig.trustSelfSignedCertificates = true;
    tlsConfig.ignoreCommonNameMismatch = true;
    
    socket = new TLSSocket(null, 0, tlsConfig);
   }
   trace("timeout=" + socket.timeout);
   
   socket.connect("inner.FlashTeam.com", 8888);
   
   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 {
   trace("onConnect()");
   socket.writeUTFBytes("GET / HTTP/1.1\n");
   socket.writeUTFBytes("Host: inner.flashteam.com\n");
   socket.writeUTFBytes("<EOF>");
   socket.flush();
  }
  
  private function onClose(e:Event):void {
   // Security error is thrown if this line is excluded
   trace("onClose()");
   socket.close();
  }
  
  private function onError(e:IOErrorEvent):void {
   trace("onError(), e=" + e);
   
   if(socket is SecureSocket){
    var serverCertificateStatus:String = (socket as SecureSocket).serverCertificateStatus;
    trace("serverCertificateStatus=" + serverCertificateStatus);
   }
  }
  
  private function onSecError(e:SecurityErrorEvent):void {
   trace("onSecError(), e=" + e);
  }
  
  private function onResponse(e:ProgressEvent):void {
   trace("onResponse()");
   
   if (socket.bytesAvailable>0) {
    trace(socket.readUTFBytes(socket.bytesAvailable));
   }
  }
 }
}

接下來,以下是 .NET Socket Server 程式碼。會起兩個執行緒分別監聽 843 與 8888 port,若是 flash client 走 843 送來 <policy-file-request/>,就會於 Proc1() 得到對應的 policy file,若從 843 port 送來其他內容則會忽略。若 flash client 走 8888 送來 <policy-file-request/>,就會於 Proc2() 得到對應的 policy file,否則 Server 會將收到的資料改大寫後再送回給 flash client。

此外,Proc2() 中,是以 SslStream 處理資料,所以若從這裡能正常解碼出的資料就表示送來的資料有編碼加密過,證明 flash 使用 SecureSocket 要求 policy file 時是有 SSL 的。

MyTcpListener.cs
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;

class MyTcpListener
{
    static X509Certificate serverCertificate = null;

    public static void Main()
    {
        X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);
        for (int i = 0; i < store.Certificates.Count; i++)
        {
            X509Certificate cer = store.Certificates[i];
            Console.WriteLine("[" + i + "]" + cer.Subject);
        }
        X509CertificateCollection collection = store.Certificates.Find(X509FindType.FindBySubjectName, "inner.FlashTeam.com", false);
        Console.WriteLine("X509CertificateCollection.Count=" + collection.Count);
        if (collection.Count == 0)
            return;
        serverCertificate = collection[0];



        Thread t = new Thread(new ThreadStart(Proc1));
        t.Start();

        Thread t2 = new Thread(new ThreadStart(Proc2));
        t2.Start();
    }

    private static void Proc1()
    {
        TcpListener server = null;
        try
        {
            // TcpListener server = new TcpListener(port);
            server = new TcpListener(IPAddress.Any, 843);

            // Start listening for client requests.
            server.Start();

            // Buffer for reading data
            Byte[] bytes = new Byte[256];
            String data = null;

            // Enter the listening loop.
            while (true)
            {
                Console.WriteLine("[Proc1]Waiting for a connection... ");

                // Perform a blocking call to accept requests.
                // You could also user server.AcceptSocket() here.
                TcpClient client = server.AcceptTcpClient();
                Console.WriteLine("[Proc1]Connected!");

                data = null;

                // Get a stream object for reading and writing
                NetworkStream stream = client.GetStream();

                int i;

                // Loop to receive all the data sent by the client.
                while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                {
                    // Translate data bytes to a ASCII string.
                    data = System.Text.Encoding.UTF8.GetString(bytes, 0, i);
                    Console.WriteLine("[Proc1]Received: {0}", data);

                    if (data.StartsWith("<policy-file-request/>"))
                    {
                        data = "<?xml version=\"1.0\"?><cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\0\r\n";

                        byte[] msg = System.Text.Encoding.UTF8.GetBytes(data);

                        // Send back a response.
                        stream.Write(msg, 0, msg.Length);
                        Console.WriteLine("[Proc1]Sent: {0}", data);
                    }
                    else
                    {
                        Console.WriteLine("[Proc1]ignore!!");
                    }
                }

                // Shutdown and end connection
                client.Close();
            }
        }
        catch (SocketException e)
        {
            Console.WriteLine("[Proc1]SocketException: {0}", e);
        }
        finally
        {
            // Stop listening for new clients.
            server.Stop();
        }


        Console.WriteLine("\n[Proc1]Hit enter to continue...");
        Console.Read();
    }

    private static void Proc2()
    {
        TcpListener server = null;
        try
        {
            // TcpListener server = new TcpListener(port);
            server = new TcpListener(IPAddress.Any, 8888);

            // Start listening for client requests.
            server.Start();

            // Buffer for reading data
            Byte[] bytes = new Byte[2048];
            String data = null;

            // Enter the listening loop.
            while (true)
            {
                Console.WriteLine("[Proc2]Waiting for a connection... ");

                // Perform a blocking call to accept requests.
                // You could also user server.AcceptSocket() here.
                TcpClient client = server.AcceptTcpClient();
                Console.WriteLine("[Proc2]Connected!");

                data = null;

                // Get a stream object for reading and writing
                NetworkStream stream = client.GetStream();
                SslStream sslStream = new SslStream(stream, false);

                sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Ssl3, true);

                int i;

                // Loop to receive all the data sent by the client.
                while ((i = sslStream.Read(bytes, 0, bytes.Length)) != 0)
                {
                    // Translate data bytes to a ASCII string.
                    data = System.Text.Encoding.UTF8.GetString(bytes, 0, i);
                    Console.WriteLine("[Proc2]Received: {0}", data);

                    if (data.StartsWith("<policy-file-request/>"))
                    {
                        data = "<?xml version=\"1.0\"?><cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\0\r\n";

                        byte[] msg = System.Text.Encoding.UTF8.GetBytes(data);

                        // Send back a response.
                        sslStream.Write(msg, 0, msg.Length);
                        Console.WriteLine("[Proc2]Sent: {0}", data);
                    }
                    else
                    {
                        data = "RECEIVE: [" + data.ToUpper() + "]";

                        byte[] msg2 = System.Text.Encoding.UTF8.GetBytes(data);

                        // Send back a response.
                        sslStream.Write(msg2, 0, msg2.Length);
                        Console.WriteLine("[Proc2]Sent: {0}", data);
                    }
                }

                // Shutdown and end connection
                client.Close();
            }
        }
        catch (SocketException e)
        {
            Console.WriteLine("[Proc2]SocketException: {0}", e);
        }
        finally
        {
            // Stop listening for new clients.
            server.Stop();
        }


        Console.WriteLine("\n[Proc2]Hit enter to continue...");
        Console.Read();
    }
}



進行測試~

首先,flash client 以 open source 的 TLSSocket 進行測試,會先向 843 以明碼索取 policy file:


因為 843 已經拿到需要的 policy file,所以可以正常的向 8888 進行溝通:


以下畫面是 Server log:




接下來,flash client 改以 SecureSocket 來測試,一開始會走 843 以加密過的 xml 來要求資料,但因為 server 不認得,所以沒有回傳正確的 policy file:


於是 flash client 只好向 8888 再用加密過的 xml 要一次,這時候 server 就看得懂了,便可以回傳加密過的 policy file 內容:


最後,flash client 就可以順利的向 8888 溝通資料了:


以下是 Server log,可以看出 Proc1() 不認得 xml 要求後,Proc2() 再次處理 <policy-file-request/> 的情況:


完成!

沒有留言: