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" \