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
This commit is contained in:
Jens Geyer 2014-04-22 22:52:43 +02:00
parent 7201c0d38f
commit c1d7943a7e
8 changed files with 568 additions and 17 deletions

View File

@ -60,6 +60,8 @@ THRIFTCODE= \
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

View File

@ -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" />

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -30,6 +30,7 @@ namespace Test
public class TestClient
{
private static int numIterations = 1;
private static string protocol = "";
public static void Execute(string[] args)
{
@ -39,7 +40,7 @@ namespace Test
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 @@ namespace Test
{
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 @@ namespace Test
Console.WriteLine(e.StackTrace);
}
//issue tests on separate threads simultaneously
Thread[] threads = new Thread[numThreads];
DateTime start = DateTime.Now;
@ -101,17 +115,22 @@ namespace Test
{
// 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 @@ namespace Test
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 @@ namespace Test
}
Console.WriteLine("}");
sbyte arg0 = 1;
int arg1 = 2;
long arg2 = long.MaxValue;

View File

@ -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 @@ namespace Test
{
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 @@ namespace Test
{
// as default
}
else if ( args[i] == "buffered" )
else if (args[i] == "buffered")
{
useBufferedSockets = true;
}
@ -351,6 +352,18 @@ namespace Test
{
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 @@ namespace Test
}
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 @@ namespace Test
// 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();

View File

@ -26,4 +26,5 @@ gmcs /out:TestClientServer.exe /reference:../../Thrift.dll /reference:ThriftImp
export MONO_PATH=../../
timeout 120 ./TestClientServer.exe server &
sleep 1
./TestClientServer.exe client

View File

@ -102,6 +102,10 @@ nodejs_protocols="binary json"
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 @@ done
# 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" \