THRIFT-2347 C# TLS Transport based on THRIFT-181
Client: C#
Patch: Beat Käslin

This closes #104

commit 21c33abd59a2333c48722933c6894d8ed145e638
 Author: Beat Kaeslin <beat.kaeslin@siemens.com>
 Date: 2014-04-16T14:07:58Z

Add TLS transport for C#

commit 60a0baa1797b0ef0ea6f8c21e5b81a78cdfcdf16
 Author: Beat Kaeslin <beat.kaeslin@siemens.com>
 Date: 2014-04-17T06:23:57Z

csharp tests moved to the end
diff --git a/lib/csharp/Makefile.am b/lib/csharp/Makefile.am
index e847237..b99674c 100644
--- a/lib/csharp/Makefile.am
+++ b/lib/csharp/Makefile.am
@@ -60,6 +60,8 @@
             src/Transport/TMemoryBuffer.cs \
             src/Transport/TNamedPipeClientTransport.cs \
             src/Transport/TNamedPipeServerTransport.cs \
+            src/Transport/TTLSSocket.cs \
+            src/Transport/TTLSServerSocket.cs \
             src/TProcessor.cs \
             src/TException.cs \
             src/TApplicationException.cs
diff --git a/lib/csharp/src/Thrift.csproj b/lib/csharp/src/Thrift.csproj
index d475ed6..1f10868 100644
--- a/lib/csharp/src/Thrift.csproj
+++ b/lib/csharp/src/Thrift.csproj
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements. See the NOTICE file
@@ -118,6 +118,8 @@
     <Compile Include="Transport\TServerTransport.cs" />
     <Compile Include="Transport\TSocket.cs" />
     <Compile Include="Transport\TStreamTransport.cs" />
+    <Compile Include="Transport\TTLSServerSocket.cs" />
+    <Compile Include="Transport\TTLSSocket.cs" />
     <Compile Include="Transport\TTransport.cs" />
     <Compile Include="Transport\TTransportException.cs" />
     <Compile Include="Transport\TTransportFactory.cs" />
diff --git a/lib/csharp/src/Transport/TTLSServerSocket.cs b/lib/csharp/src/Transport/TTLSServerSocket.cs
new file mode 100644
index 0000000..948b1d7
--- /dev/null
+++ b/lib/csharp/src/Transport/TTLSServerSocket.cs
@@ -0,0 +1,186 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Thrift.Transport
+{
+	/// <summary>
+	/// SSL Server Socket Wrapper Class
+	/// </summary>
+	public class TTLSServerSocket : TServerTransport
+	{
+		/// <summary>
+		/// Underlying tcp server
+		/// </summary>
+		private TcpListener server = null;
+
+		/// <summary>
+		/// The port where the socket listen
+		/// </summary>
+		private int port = 0;
+
+		/// <summary>
+		/// Timeout for the created server socket
+		/// </summary>
+		private int clientTimeout = 0;
+
+		/// <summary>
+		/// Whether or not to wrap new TSocket connections in buffers
+		/// </summary>
+		private bool useBufferedSockets = false;
+
+		/// <summary>
+		/// The servercertificate with the private- and public-key
+		/// </summary>
+		private X509Certificate serverCertificate;
+
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="TTLSServerSocket" /> class.
+		/// </summary>
+		/// <param name="port">The port where the server runs.</param>
+		/// <param name="certificate">The certificate object.</param>
+		public TTLSServerSocket(int port, X509Certificate2 certificate)
+			: this(port,  0, certificate)
+		{
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="TTLSServerSocket" /> class.
+		/// </summary>
+		/// <param name="port">The port where the server runs.</param>
+		/// <param name="clientTimeout">Send/receive timeout.</param>
+		/// <param name="certificate">The certificate object.</param>
+		public TTLSServerSocket(int port, int clientTimeout, X509Certificate2 certificate)
+			: this(port, clientTimeout, false, certificate)
+		{
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="TTLSServerSocket" /> class.
+		/// </summary>
+		/// <param name="port">The port where the server runs.</param>
+		/// <param name="clientTimeout">Send/receive timeout.</param>
+		/// <param name="useBufferedSockets">If set to <c>true</c> [use buffered sockets].</param>
+		/// <param name="certificate">The certificate object.</param>
+		public TTLSServerSocket(int port, int clientTimeout, bool useBufferedSockets, X509Certificate2 certificate)
+		{
+			if (!certificate.HasPrivateKey)
+			{
+				throw new TTransportException(TTransportException.ExceptionType.Unknown, "Your server-certificate needs to have a private key");
+			}
+
+			this.port = port;
+			this.serverCertificate = certificate;
+			this.useBufferedSockets = useBufferedSockets;
+			try
+			{
+				// Create server socket
+				server = new TcpListener(System.Net.IPAddress.Any, this.port);
+				server.Server.NoDelay = true;
+			}
+			catch (Exception)
+			{
+				server = null;
+				throw new TTransportException("Could not create ServerSocket on port " + port + ".");
+			}
+		}
+
+		/// <summary>
+		/// Starts the server.
+		/// </summary>
+		public override void Listen()
+		{
+			// Make sure accept is not blocking
+			if (this.server != null)
+			{
+				try
+				{
+					this.server.Start();
+				}
+				catch (SocketException sx)
+				{
+					throw new TTransportException("Could not accept on listening socket: " + sx.Message);
+				}
+			}
+		}
+
+		/// <summary>
+		/// Callback for Accept Implementation
+		/// </summary>
+		/// <returns>
+		/// TTransport-object.
+		/// </returns>
+		protected override TTransport AcceptImpl()
+		{
+			if (this.server == null)
+			{
+				throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No underlying server socket.");
+			}
+
+			try
+			{
+				TcpClient client = this.server.AcceptTcpClient();
+				client.SendTimeout = client.ReceiveTimeout = this.clientTimeout;
+
+				//wrap the client in an SSL Socket passing in the SSL cert
+				TTLSSocket socket = new TTLSSocket(client, this.serverCertificate, true);
+
+				socket.setupTLS();
+
+				if (useBufferedSockets)
+				{
+					TBufferedTransport trans = new TBufferedTransport(socket);
+					return trans;
+				}
+				else
+				{
+					return socket;
+				}
+				
+			}
+			catch (Exception ex)
+			{
+				throw new TTransportException(ex.ToString());
+			}
+		}
+
+		/// <summary>
+		/// Stops the Server
+		/// </summary>
+		public override void Close()
+		{
+			if (this.server != null)
+			{
+				try
+				{
+					this.server.Stop();
+				}
+				catch (Exception ex)  
+				{
+					throw new TTransportException("WARNING: Could not close server socket: " + ex);
+				}
+				this.server = null;
+			}
+		}
+	}
+}
diff --git a/lib/csharp/src/Transport/TTLSSocket.cs b/lib/csharp/src/Transport/TTLSSocket.cs
new file mode 100644
index 0000000..beb5876
--- /dev/null
+++ b/lib/csharp/src/Transport/TTLSSocket.cs
@@ -0,0 +1,284 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Thrift.Transport
+{
+	/// <summary>
+	/// SSL Socket Wrapper class
+	/// </summary>
+	public class TTLSSocket : TStreamTransport
+	{
+		/// <summary>
+		/// Internal TCP Client
+		/// </summary>
+		private TcpClient client = null;
+
+		/// <summary>
+		/// The host
+		/// </summary>
+		private string host = null;
+
+		/// <summary>
+		/// The port
+		/// </summary>
+		private int port = 0;
+
+		/// <summary>
+		/// The timeout for the connection
+		/// </summary>
+		private int timeout = 0;
+
+		/// <summary>
+		/// Internal SSL Stream for IO
+		/// </summary>
+		private SslStream secureStream = null;
+
+		/// <summary>
+		/// Defines wheter or not this socket is a server socket<br/>
+		/// This is used for the TLS-authentication
+		/// </summary>
+		private bool isServer = false;
+
+		/// <summary>
+		/// The certificate
+		/// </summary>
+		private X509Certificate certificate = null;
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="TTLSSocket"/> class.
+		/// </summary>
+		/// <param name="client">An already created TCP-client</param>
+		/// <param name="certificate">The certificate.</param>
+		/// <param name="isServer">if set to <c>true</c> [is server].</param>
+		public TTLSSocket(TcpClient client, X509Certificate certificate, bool isServer = false)
+		{
+			this.client = client;
+			this.certificate = certificate;
+			this.isServer = isServer;
+
+			if (IsOpen)
+			{
+				base.inputStream = client.GetStream();
+				base.outputStream = client.GetStream();
+			}
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="TTLSSocket"/> class.
+		/// </summary>
+		/// <param name="host">The host, where the socket should connect to.</param>
+		/// <param name="port">The port.</param>
+		/// <param name="certificatePath">The certificate path.</param>
+		public TTLSSocket(string host, int port, string certificatePath)
+			: this(host, port, 0, X509Certificate.CreateFromCertFile(certificatePath))
+		{
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="TTLSSocket"/> class.
+		/// </summary>
+		/// <param name="host">The host, where the socket should connect to.</param>
+		/// <param name="port">The port.</param>
+		/// <param name="certificate">The certificate.</param>
+		public TTLSSocket(string host, int port, X509Certificate certificate)
+			: this(host, port, 0, certificate)
+		{
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="TTLSSocket"/> class.
+		/// </summary>
+		/// <param name="host">The host, where the socket should connect to.</param>
+		/// <param name="port">The port.</param>
+		/// <param name="timeout">The timeout.</param>
+		/// <param name="certificate">The certificate.</param>
+		public TTLSSocket(string host, int port, int timeout, X509Certificate certificate)
+		{
+			this.host = host;
+			this.port = port;
+			this.timeout = timeout;
+			this.certificate = certificate;
+
+			InitSocket();
+		}
+
+		/// <summary>
+		/// Creates the TcpClient and sets the timeouts
+		/// </summary>
+		private void InitSocket()
+		{
+			this.client = new TcpClient();
+			client.ReceiveTimeout = client.SendTimeout = timeout;
+			client.Client.NoDelay = true;
+		}
+
+		/// <summary>
+		/// Sets Send / Recv Timeout for IO
+		/// </summary>
+		public int Timeout
+		{
+			set
+			{
+				this.client.ReceiveTimeout = this.client.SendTimeout = this.timeout = value;
+			}
+		}
+
+		/// <summary>
+		/// Gets the TCP client.
+		/// </summary>
+		public TcpClient TcpClient
+		{
+			get
+			{
+				return client;
+			}
+		}
+
+		/// <summary>
+		/// Gets the host.
+		/// </summary>
+		public string Host
+		{
+			get
+			{
+				return host;
+			}
+		}
+
+		/// <summary>
+		/// Gets the port.
+		/// </summary>
+		public int Port
+		{
+			get
+			{
+				return port;
+			}
+		}
+
+		/// <summary>
+		/// Gets a value indicating whether TCP Client is Cpen 
+		/// </summary>
+		public override bool IsOpen
+		{
+			get
+			{
+				if (this.client == null)
+				{
+					return false;
+				}
+
+				return this.client.Connected;
+			}
+		}
+
+		/// <summary>
+		/// Validates the certificates!<br/>
+		/// </summary>
+		/// <param name="sender">The sender-object.</param>
+		/// <param name="certificate">The used certificate.</param>
+		/// <param name="chain">The certificate chain.</param>
+		/// <param name="sslPolicyErrors">An enum, which lists all the errors from the .NET certificate check.</param>
+		/// <returns></returns>
+		private bool CertificateValidator(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslValidationErrors)
+		{
+			return (sslValidationErrors == SslPolicyErrors.None);
+		}
+
+		/// <summary>
+		/// Connects to the host and starts the routine, which sets up the TLS
+		/// </summary>
+		public override void Open()
+		{
+			if (IsOpen)
+			{
+				throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected");
+			}
+
+			if (String.IsNullOrEmpty(host))
+			{
+				throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host");
+			}
+
+			if (port <= 0)
+			{
+				throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port");
+			}
+
+			if (client == null)
+			{
+				InitSocket();
+			}
+
+			client.Connect(host, port);
+
+			setupTLS();
+		}
+
+		/// <summary>
+		/// Creates a TLS-stream and lays it over the existing socket
+		/// </summary>
+		public void setupTLS()
+		{
+			if (isServer)
+			{
+				// Server authentication
+				this.secureStream = new SslStream(this.client.GetStream(), false);
+				this.secureStream.AuthenticateAsServer(this.certificate, false, SslProtocols.Tls, true);
+			}
+			else
+			{
+				// Client authentication
+				X509CertificateCollection validCerts = new X509CertificateCollection();
+				validCerts.Add(certificate);
+
+				this.secureStream = new SslStream(this.client.GetStream(), false, new RemoteCertificateValidationCallback(CertificateValidator));
+				this.secureStream.AuthenticateAsClient(host, validCerts, SslProtocols.Tls, true);
+			}
+
+			inputStream = this.secureStream;
+			outputStream = this.secureStream;
+		}
+
+		/// <summary>
+		/// Closes the SSL Socket
+		/// </summary>
+		public override void Close()
+		{
+			base.Close();
+			if (this.client != null)
+			{
+				this.client.Close();
+				this.client = null;
+			}
+
+			if (this.secureStream != null)
+			{
+				this.secureStream.Close();
+				this.secureStream = null;
+			}
+		}
+	}
+}
diff --git a/lib/csharp/test/ThriftTest/TestClient.cs b/lib/csharp/test/ThriftTest/TestClient.cs
index ba2d4d0..593169f 100644
--- a/lib/csharp/test/ThriftTest/TestClient.cs
+++ b/lib/csharp/test/ThriftTest/TestClient.cs
@@ -30,6 +30,7 @@
 	public class TestClient
 	{
 		private static int numIterations = 1;
+		private static string protocol = "";
 
 		public static void Execute(string[] args)
 		{
@@ -39,7 +40,7 @@
 				int port = 9090;
 				string url = null, pipe = null;
 				int numThreads = 1;
-				bool buffered = false, framed = false;
+				bool buffered = false, framed = false, encrypted = false;
 
 				try
 				{
@@ -81,6 +82,21 @@
 						{
 							numThreads = Convert.ToInt32(args[++i]);
 						}
+						else if (args[i] == "-ssl")
+						{
+							encrypted = true;
+							Console.WriteLine("Using encrypted transport");
+						}
+						else if (args[i] == "-compact")
+						{
+							protocol = "compact";
+							Console.WriteLine("Using compact protocol");
+						}
+						else if (args[i] == "-json")
+						{
+							protocol = "json";
+							Console.WriteLine("Using JSON protocol");
+						}
 					}
 				}
 				catch (Exception e)
@@ -88,8 +104,6 @@
 					Console.WriteLine(e.StackTrace);
 				}
 
-
-
 				//issue tests on separate threads simultaneously
 				Thread[] threads = new Thread[numThreads];
 				DateTime start = DateTime.Now;
@@ -101,17 +115,22 @@
 					{
 						// endpoint transport
 						TTransport trans = null;
-						if( pipe != null)
+						if (pipe != null)
 							trans = new TNamedPipeClientTransport(pipe);
 						else
-							trans = new TSocket(host, port);
-						
+						{
+							if (encrypted)
+								trans = new TTLSSocket(host, port, "../../../../../keys/client.pem");
+							else
+								trans = new TSocket(host, port);
+						}
+
 						// layered transport
 						if (buffered)
 							trans = new TBufferedTransport(trans as TStreamTransport);
 						if (framed)
 							trans = new TFramedTransport(trans);
-						
+
 						//ensure proper open/close of transport
 						trans.Open();
 						trans.Close();
@@ -151,9 +170,15 @@
 
 		public static void ClientTest(TTransport transport)
 		{
-			TBinaryProtocol binaryProtocol = new TBinaryProtocol(transport);
+			TProtocol proto;
+			if (protocol == "compact")
+				proto = new TCompactProtocol(transport);
+			else if (protocol == "json")
+				proto = new TJSONProtocol(transport);
+			else
+				proto = new TBinaryProtocol(transport);
 
-			ThriftTest.Client client = new ThriftTest.Client(binaryProtocol);
+			ThriftTest.Client client = new ThriftTest.Client(proto);
 			try
 			{
 				if (!transport.IsOpen)
@@ -430,7 +455,6 @@
 			}
 			Console.WriteLine("}");
 
-
 			sbyte arg0 = 1;
 			int arg1 = 2;
 			long arg2 = long.MaxValue;
diff --git a/lib/csharp/test/ThriftTest/TestServer.cs b/lib/csharp/test/ThriftTest/TestServer.cs
index 965a7de..f0e9abb 100644
--- a/lib/csharp/test/ThriftTest/TestServer.cs
+++ b/lib/csharp/test/ThriftTest/TestServer.cs
@@ -23,6 +23,7 @@
 // http://developers.facebook.com/thrift/
 using System;
 using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
 using Thrift.Collections;
 using Thrift.Test; //generated code
 using Thrift.Transport;
@@ -321,7 +322,7 @@
 		{
 			try
 			{
-				bool useBufferedSockets = false, useFramed = false;
+				bool useBufferedSockets = false, useFramed = false, useEncryption = false, compact = false, json = false;
 				int port = 9090, i = 0;
 				string pipe = null;
 				if (args.Length > 0)
@@ -343,7 +344,7 @@
 						{
 							// as default
 						}
-						else if ( args[i] == "buffered" )
+						else if (args[i] == "buffered")
 						{
 							useBufferedSockets = true;
 						}
@@ -351,6 +352,18 @@
 						{
 							useFramed = true;
 						}
+						else if (args[i] == "ssl")
+						{
+							useEncryption = true;
+						}
+						else if (args[i] == "compact" )
+						{
+							compact = true;
+						}
+						else if (args[i] == "json" )
+						{
+							json = true;
+						}
 						else
 						{
 							// Fall back to the older boolean syntax
@@ -371,15 +384,30 @@
 				}
 				else
 				{
-					trans = new TServerSocket(port, 0, useBufferedSockets);
+					if (useEncryption)
+					{
+						trans = new TTLSServerSocket(port, 0, useBufferedSockets, new X509Certificate2("../../../../../keys/server.pem"));
+					}
+					else
+					{
+						trans = new TServerSocket(port, 0, useBufferedSockets);
+					}
 				}
 
+				TProtocolFactory proto;
+				if ( compact )
+					proto = new TCompactProtocol.Factory();
+				else if ( json )
+					proto = new TJSONProtocol.Factory();
+				else
+					proto = new TBinaryProtocol.Factory();
+					
 				// Simple Server
 				TServer serverEngine;
 				if ( useFramed )
-					serverEngine = new TSimpleServer(testProcessor, trans, new TFramedTransport.Factory());
+					serverEngine = new TSimpleServer(testProcessor, trans, new TFramedTransport.Factory(), proto);
 				else
-					serverEngine = new TSimpleServer(testProcessor, trans);
+					serverEngine = new TSimpleServer(testProcessor, trans, new TTransportFactory(), proto);
 
 				// ThreadPool Server
 				// serverEngine = new TThreadPoolServer(testProcessor, tServerSocket);
@@ -391,9 +419,11 @@
 
 				// Run it
 				string where = ( pipe != null ? "on pipe "+pipe : "on port " + port);
-				Console.WriteLine("Starting the server " +where+
+				Console.WriteLine("Starting the server " + where +
 					(useBufferedSockets ? " with buffered socket" : "") + 
 					(useFramed ? " with framed transport" : "") + 
+					(useEncryption ? " with encryption" : "") + 
+					(compact ? " with compact protocol" : "") + 
 					"...");
 				serverEngine.Serve();
 
diff --git a/lib/csharp/test/ThriftTest/maketest.sh b/lib/csharp/test/ThriftTest/maketest.sh
index 86c1a11..210a523 100644
--- a/lib/csharp/test/ThriftTest/maketest.sh
+++ b/lib/csharp/test/ThriftTest/maketest.sh
@@ -26,4 +26,5 @@
 export MONO_PATH=../../
 
 timeout 120 ./TestClientServer.exe server &
+sleep 1
 ./TestClientServer.exe client
diff --git a/test/test.sh b/test/test.sh
index b73635f..91ac175 100755
--- a/test/test.sh
+++ b/test/test.sh
@@ -102,6 +102,10 @@
 nodejs_transports="buffered framed"
 nodejs_sockets="ip ip-ssl"
 
+csharp_protocols="binary compact json"
+csharp_transports="buffered framed"
+csharp_sockets="ip ip-ssl"
+
 ant -f ../lib/java/build.xml compile-test 1>/dev/null
 
 ######### java client - java server #############
@@ -209,6 +213,24 @@
 # delete Unix Domain Socket used by cpp tests
 rm -f /tmp/ThriftTest.thrift
 
+######### csharp client - csharp server #############
+export MONO_PATH=../lib/csharp
+for proto in $csharp_protocols; do
+  for trans in $csharp_transports; do
+    for sock in $csharp_sockets; do
+      case "$sock" in
+        "ip" ) extraparam="";;
+        "ip-ssl" ) extraparam="--ssl";;
+      esac
+      do_test "csharp-csharp"   "${proto}" "${trans}-${sock}" \
+              "../lib/csharp/test/ThriftTest/TestClientServer.exe client --protocol=${proto} --transport=${trans} ${extraparam}" \
+              "../lib/csharp/test/ThriftTest/TestClientServer.exe server --protocol=${proto} --transport=${trans} ${extraparam}" \
+              "10" "10"
+    done
+  done
+done
+
+
 do_test "py-py" "binary" "buffered-ip" \
         "py/TestClient.py --proto=binary --port=9090 --host=localhost --genpydir=py/gen-py" \
         "py/TestServer.py --proto=binary --port=9090 --genpydir=py/gen-py TSimpleServer" \