Repository: thrift Updated Branches: refs/heads/master 7201c0d38 -> c1d7943a7
THRIFT-2347 C# TLS Transport based on THRIFT-181 Client: C# Patch: Beat Käslin This closes #104 commit 21c33abd59a2333c48722933c6894d8ed145e638 Author: Beat Kaeslin <[email protected]> Date: 2014-04-16T14:07:58Z Add TLS transport for C# commit 60a0baa1797b0ef0ea6f8c21e5b81a78cdfcdf16 Author: Beat Kaeslin <[email protected]> Date: 2014-04-17T06:23:57Z csharp tests moved to the end Project: http://git-wip-us.apache.org/repos/asf/thrift/repo Commit: http://git-wip-us.apache.org/repos/asf/thrift/commit/c1d7943a Tree: http://git-wip-us.apache.org/repos/asf/thrift/tree/c1d7943a Diff: http://git-wip-us.apache.org/repos/asf/thrift/diff/c1d7943a Branch: refs/heads/master Commit: c1d7943a7ed78fb434eaa90feb1a3a17b446fc97 Parents: 7201c0d Author: Jens Geyer <[email protected]> Authored: Tue Apr 22 22:52:43 2014 +0200 Committer: Jens Geyer <[email protected]> Committed: Tue Apr 22 22:52:43 2014 +0200 ---------------------------------------------------------------------- lib/csharp/Makefile.am | 2 + lib/csharp/src/Thrift.csproj | 4 +- lib/csharp/src/Transport/TTLSServerSocket.cs | 186 ++++++++++++++ lib/csharp/src/Transport/TTLSSocket.cs | 284 ++++++++++++++++++++++ lib/csharp/test/ThriftTest/TestClient.cs | 46 +++- lib/csharp/test/ThriftTest/TestServer.cs | 42 +++- lib/csharp/test/ThriftTest/maketest.sh | 1 + test/test.sh | 22 ++ 8 files changed, 569 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/thrift/blob/c1d7943a/lib/csharp/Makefile.am ---------------------------------------------------------------------- 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 @@ 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 http://git-wip-us.apache.org/repos/asf/thrift/blob/c1d7943a/lib/csharp/src/Thrift.csproj ---------------------------------------------------------------------- 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" /> http://git-wip-us.apache.org/repos/asf/thrift/blob/c1d7943a/lib/csharp/src/Transport/TTLSServerSocket.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; + } + } + } +} http://git-wip-us.apache.org/repos/asf/thrift/blob/c1d7943a/lib/csharp/src/Transport/TTLSSocket.cs ---------------------------------------------------------------------- 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; + } + } + } +} http://git-wip-us.apache.org/repos/asf/thrift/blob/c1d7943a/lib/csharp/test/ThriftTest/TestClient.cs ---------------------------------------------------------------------- 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 @@ 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); - - ThriftTest.Client client = new ThriftTest.Client(binaryProtocol); + 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(proto); try { if (!transport.IsOpen) @@ -430,7 +455,6 @@ namespace Test } Console.WriteLine("}"); - sbyte arg0 = 1; int arg1 = 2; long arg2 = long.MaxValue; http://git-wip-us.apache.org/repos/asf/thrift/blob/c1d7943a/lib/csharp/test/ThriftTest/TestServer.cs ---------------------------------------------------------------------- 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 @@ 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(); http://git-wip-us.apache.org/repos/asf/thrift/blob/c1d7943a/lib/csharp/test/ThriftTest/maketest.sh ---------------------------------------------------------------------- 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 @@ gmcs /out:TestClientServer.exe /reference:../../Thrift.dll /reference:ThriftImp export MONO_PATH=../../ timeout 120 ./TestClientServer.exe server & +sleep 1 ./TestClientServer.exe client http://git-wip-us.apache.org/repos/asf/thrift/blob/c1d7943a/test/test.sh ---------------------------------------------------------------------- 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_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" \
