1.
將上一篇,幫 IIS 安裝的 self-signed certification,匯出 private key 的 *.pfx 檔案 "借" 來使用:
2.
在要執行 socket server 的環境中,安裝 *.pfx,點兩下進行安裝~
安裝憑證的位置,你可以選擇由精靈自己判斷決定:
或自己選擇安裝到 "受信任的根憑證授權單位":
以下,我會先以精靈決定的預設位置進行安裝 (會被安裝到 "個人憑證"),後續也將會遇到一些問題,說明後,會再改將憑證安裝到 "受信任的根憑證授權單位" 以解決下面幾個問題。
3.
安裝後,可於 主控台 (執行 mmc.exe 可叫出) 中,新增管理單元:"憑證",可進行管理作業:
剛提到,預設這個自己簽名的憑證會被安裝到 "個人" 中,若你需要將他移到 "受信任的根憑證授權單位",只要將此憑證 "拖曳" 到不同分類下即可。
Server 憑證準備好了!
4.
接下來要開發 socket server,這裡我直接使用 MSDN 提供的範例:
SslStream 類別
http://msdn.microsoft.com/zh-tw/library/system.net.security.sslstream%28v=VS.100%29.aspx
不過此範例是執行時須帶入憑證檔,我做些調整,改成抓作業系統中已經安裝的憑證檔,
SslTcpServer.cs
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;
namespace Examples.System.Net
{
public sealed class SslTcpServer
{
static X509Certificate serverCertificate = null;
// The certificate parameter specifies the name of the file
// containing the machine certificate.
public static void RunServer(string certificate)
{
//serverCertificate = X509Certificate.CreateFromCertFile(certificate);
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
for(int i=0; i<store.Certificates.Count; i++){
X509Certificate cer = store.Certificates[i];
trace("[" + i + "]" + cer.Subject);
}
X509CertificateCollection collection = store.Certificates.Find(X509FindType.FindBySubjectName, "inner.FlashTeam.com", false);
trace("X509CertificateCollection.Count=" + collection.Count);
if (collection.Count == 0)
return;
serverCertificate = collection[0];
// Create a TCP/IP (IPv4) socket and listen for incoming connections.
TcpListener listener = new TcpListener(IPAddress.Any, 8080);
listener.Start();
while (true)
{
trace("Waiting for a client to connect...");
// Application blocks while waiting for an incoming connection.
// Type CNTL-C to terminate the server.
TcpClient client = listener.AcceptTcpClient();
ProcessClient(client);
}
}
static void ProcessClient(TcpClient client)
{
// A client has connected. Create the
// SslStream using the client's network stream.
SslStream sslStream = new SslStream(
client.GetStream(), false);
// Authenticate the server but don't require the client to authenticate.
try
{
sslStream.AuthenticateAsServer(serverCertificate,
false, SslProtocols.Tls, true);
// Display the properties and settings for the authenticated stream.
DisplaySecurityLevel(sslStream);
DisplaySecurityServices(sslStream);
DisplayCertificateInformation(sslStream);
DisplayStreamProperties(sslStream);
// Set timeouts for the read and write to 5 seconds.
sslStream.ReadTimeout = 5000;
sslStream.WriteTimeout = 5000;
// Read a message from the client.
trace("Waiting for client message...");
string messageData = ReadMessage(sslStream);
trace("Received: " + messageData);
// Write a message to the client.
byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
trace("Sending hello message.");
sslStream.Write(message);
}
catch (AuthenticationException e)
{
trace("Exception: " + e.Message);
if (e.InnerException != null)
{
trace("Inner exception: " + e.InnerException.Message);
}
trace("Authentication failed - closing the connection.");
sslStream.Close();
client.Close();
return;
}
finally
{
// The client stream will be closed with the sslStream
// because we specified this behavior when creating
// the sslStream.
sslStream.Close();
client.Close();
}
}
static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the client.
// The client signals the end of the message using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do
{
// Read the client's test message.
bytes = sslStream.Read(buffer, 0, buffer.Length);
// Use Decoder class to convert from bytes to UTF8
// in case a character spans two buffers.
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
decoder.GetChars(buffer, 0, bytes, chars, 0);
messageData.Append(chars);
// Check for EOF or an empty message.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes != 0);
return messageData.ToString();
}
static void DisplaySecurityLevel(SslStream stream)
{
trace("===DisplaySecurityLevel===");
trace("Cipher: " + stream.CipherAlgorithm + ", strength: " + stream.CipherStrength);
trace("Hash: " + stream.HashAlgorithm + ", strength: " + stream.HashStrength);
trace("Key exchange: " + stream.KeyExchangeAlgorithm + ", strength: " + stream.KeyExchangeStrength);
trace("Protocol: " + stream.SslProtocol);
}
static void DisplaySecurityServices(SslStream stream)
{
trace("===DisplaySecurityServices===");
trace("Is authenticated: " + stream.IsAuthenticated + ", as server? " + stream.IsServer);
trace("IsSigned: " + stream.IsSigned);
trace("Is Encrypted: " + stream.IsEncrypted);
}
static void DisplayStreamProperties(SslStream stream)
{
trace("===DisplayStreamProperties===");
trace("Can read: " + stream.CanRead + ", write: " + stream.CanWrite);
trace("Can timeout: " + stream.CanTimeout);
}
static void DisplayCertificateInformation(SslStream stream)
{
trace("===DisplayCertificateInformation===");
trace("Certificate revocation list checked: " + stream.CheckCertRevocationStatus);
X509Certificate localCertificate = stream.LocalCertificate;
if (stream.LocalCertificate != null)
{
trace("Local cert was issued to " + localCertificate.Subject + " and is valid from " + localCertificate.GetEffectiveDateString() + " until " + localCertificate.GetExpirationDateString() + ".");
}
else
{
trace("Local certificate is null.");
}
// Display the properties of the client's certificate.
X509Certificate remoteCertificate = stream.RemoteCertificate;
if (stream.RemoteCertificate != null)
{
trace("Remote cert was issued to " + remoteCertificate.Subject + " and is valid from " + remoteCertificate.GetEffectiveDateString() + " until " + remoteCertificate.GetExpirationDateString() + ".");
}
else
{
trace("Remote certificate is null.");
}
}
private static void DisplayUsage()
{
trace("===DisplayUsage===");
trace("To start the server specify:");
trace("serverSync certificateFile.cer");
Environment.Exit(1);
}
public static int Main(string[] args)
{
string certificate = null;
/*
if (args == null || args.Length < 1)
{
DisplayUsage();
}
certificate = args[0];
*/
SslTcpServer.RunServer(certificate);
return 0;
}
private static void trace(String msg)
{
String dt = DateTime.Now.ToString("hh:mm:ss");
Console.WriteLine("[" + dt + "]" + msg);
}
}
}
上面程式碼中,需特別注意的地方有:
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
與
X509CertificateCollection collection = store.Certificates.Find(X509FindType.FindBySubjectName, "inner.FlashTeam.com", false);
決定了從哪種分類中找尋憑證,以及用何種方式搜尋並取得憑證。
5.
準備 flash client 端的程式
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 var socket:Socket;
public function TestSecureSocket()
{
trace(SecureSocket.isSupported)
Security.allowDomain("*");
var sslsocket:SecureSocket;
sslsocket = new SecureSocket();
socket = sslsocket;
socket.connect("127.0.0.1", 8080);
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>");
}
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));
}
}
}
}
因為在 socket server 端,是採用僅針對 server 作認證,不要求 client 端一定要提供認證的作法,所以這裡不用包含認證檔。
6.
整合測試,先啟動 server,再執行 client,
server log 如下畫面:
client log 如下畫面:
可以看到 flash 得到 serverCertificateStatus=untrustedSigners 的 IOErrorEvent。
調整一下 socket server 所在作業系統的憑證安裝位置,移到 "受信任的根憑證授權單位" 中,因為移動了安裝位置,所以 Server 必須調整找憑證的程式碼:
X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
然後重新啟動 Server,再重新啟動 flash 測試,得到以下畫面:
可以看到 flash 得到 serverCertificateStatus=principalMismatch 的 IOErrorEvent。
原因是出在 socket.connect("127.0.0.1", 8080),若改為 socket.connect("inner.FlashTeam.com", 8080),便可以看到以下的正確執行結果:
server log:
client log:
7.
步驟 1 的 server 憑證,是從 IIS "借" 來的,若是想要自己透過 openssl 來製作的話,簡單說明如下:
* 安裝 openssl,有人已經編譯好 windows 平台使用的版本
http://www.slproweb.com/products/Win32OpenSSL.html
安裝 Win32 OpenSSL v1.0.0e (8mb)
若有需要的人,需先安裝一下 Visual C++ 2008 Redistributables
* 看到很多文件都提到 linux 平台,需要設定環境變數,所以我想 windows 平台也是,請在環境變數中增加
OPENSSL_CONF = C:\OpenSSL\bin\openssl.cfg
* 準備要來製作憑證,參考以下兩篇文章:
如何製作 SSL X.509 憑證?
http://www.study-area.org/tips/certs/certs.html
Openssl常用命令速查
http://www.myssl.cn/guide/faq_openssl_command.asp
* 開始建立憑證:
參考第一份文件,進行以下三個步驟
openssl genrsa -des3 -out myrootca.key 2048
openssl x509 -req -days 3650 -sha1 -extensions v3_ca -signkey myrootca.key -in myrootca.req -out myrootca.crt
參考第二份文件,進行以下三個步驟
openssl req -new -nodes -keyout myrootca.key -out myrootca.csr
openssl x509 -req -days 3650 -sha1 -extensions v3_ca -signkey myrootca.key -in myrootca.csr -out myrootca.cer
openssl pkcs12 -export -out myrootca.pfx -inkey myrootca.key -in myrootca.cer
以上步驟我就不解釋了,我不是專家,怕解釋不清楚,總之最後就可以得到 myrootca.pfx 啦~












3 意見:
這篇的測試,都是本機開發測試,.net socket server 不需要提供 policy file (crossdomain.xml),不過當實際放到跨網域的架構時,即使加入於 port 843 提供 policy file 的相關程式,卻不斷發生一些問題,還在研究中~
現在使用 sniffer 觀察的結果,當使用 SecureSocket 時,就連 FP 向 Socket 索取 policy file 時都會使用 SSL,若 843 port 沒有用 SSL 接資料,就會收到亂碼無法處理,接著就會向原本的 port 索取 policy file,若原本的 port 有使用 SSL 來監聽,就可以看到 < policy-file-request /> 。
因為我手邊寫的 .NET server 還有點問題,所以還無法完整確認這點~
解法:
使用 SecureSocket 連接 Socket Server - Part 2
http://blog.ben.idv.tw/2011/11/securesocket-socket-server-part-2.html
張貼意見