2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
21 using System.Net.Security;
22 using System.Net.Sockets;
23 using System.Security.Authentication;
24 using System.Security.Cryptography.X509Certificates;
26 namespace Thrift.Transport
29 /// SSL Socket Wrapper class
31 public class TTLSSocket : TStreamTransport
34 /// Internal TCP Client
36 private TcpClient client;
49 /// The timeout for the connection
54 /// Internal SSL Stream for IO
56 private SslStream secureStream;
59 /// Defines wheter or not this socket is a server socket<br/>
60 /// This is used for the TLS-authentication
62 private bool isServer;
67 private X509Certificate certificate;
70 /// User defined certificate validator.
72 private RemoteCertificateValidationCallback certValidator;
75 /// The function to determine which certificate to use.
77 private LocalCertificateSelectionCallback localCertificateSelectionCallback;
80 /// The SslProtocols value that represents the protocol used for authentication.SSL protocols to be used.
82 private readonly SslProtocols sslProtocols;
85 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
87 /// <param name="client">An already created TCP-client</param>
88 /// <param name="certificate">The certificate.</param>
89 /// <param name="isServer">if set to <c>true</c> [is server].</param>
90 /// <param name="certValidator">User defined cert validator.</param>
91 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
92 /// <param name="sslProtocols">The SslProtocols value that represents the protocol used for authentication.</param>
95 X509Certificate certificate,
96 bool isServer = false,
97 RemoteCertificateValidationCallback certValidator = null,
98 LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
99 // TODO: Enable Tls11 and Tls12 (TLS 1.1 and 1.2) by default once we start using .NET 4.5+.
100 SslProtocols sslProtocols = SslProtocols.Tls)
102 this.client = client;
103 this.certificate = certificate;
104 this.certValidator = certValidator;
105 this.localCertificateSelectionCallback = localCertificateSelectionCallback;
106 this.sslProtocols = sslProtocols;
107 this.isServer = isServer;
108 if (isServer && certificate == null)
110 throw new ArgumentException("TTLSSocket needs certificate to be used for server", "certificate");
115 base.inputStream = client.GetStream();
116 base.outputStream = client.GetStream();
121 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
123 /// <param name="host">The host, where the socket should connect to.</param>
124 /// <param name="port">The port.</param>
125 /// <param name="certificatePath">The certificate path.</param>
126 /// <param name="certValidator">User defined cert validator.</param>
127 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
128 /// <param name="sslProtocols">The SslProtocols value that represents the protocol used for authentication.</param>
132 string certificatePath,
133 RemoteCertificateValidationCallback certValidator = null,
134 LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
135 SslProtocols sslProtocols = SslProtocols.Tls)
136 : this(host, port, 0, X509Certificate.CreateFromCertFile(certificatePath), certValidator, localCertificateSelectionCallback, sslProtocols)
141 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
143 /// <param name="host">The host, where the socket should connect to.</param>
144 /// <param name="port">The port.</param>
145 /// <param name="certificate">The certificate.</param>
146 /// <param name="certValidator">User defined cert validator.</param>
147 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
148 /// <param name="sslProtocols">The SslProtocols value that represents the protocol used for authentication.</param>
152 X509Certificate certificate = null,
153 RemoteCertificateValidationCallback certValidator = null,
154 LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
155 SslProtocols sslProtocols = SslProtocols.Tls)
156 : this(host, port, 0, certificate, certValidator, localCertificateSelectionCallback, sslProtocols)
161 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
163 /// <param name="host">The host, where the socket should connect to.</param>
164 /// <param name="port">The port.</param>
165 /// <param name="timeout">The timeout.</param>
166 /// <param name="certificate">The certificate.</param>
167 /// <param name="certValidator">User defined cert validator.</param>
168 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
169 /// <param name="sslProtocols">The SslProtocols value that represents the protocol used for authentication.</param>
174 X509Certificate certificate,
175 RemoteCertificateValidationCallback certValidator = null,
176 LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
177 SslProtocols sslProtocols = SslProtocols.Tls)
181 this.timeout = timeout;
182 this.certificate = certificate;
183 this.certValidator = certValidator;
184 this.localCertificateSelectionCallback = localCertificateSelectionCallback;
185 this.sslProtocols = sslProtocols;
191 /// Creates the TcpClient and sets the timeouts
193 private void InitSocket()
195 client = TSocketVersionizer.CreateTcpClient();
196 client.ReceiveTimeout = client.SendTimeout = timeout;
197 client.Client.NoDelay = true;
201 /// Sets Send / Recv Timeout for IO
207 this.client.ReceiveTimeout = this.client.SendTimeout = this.timeout = value;
212 /// Gets the TCP client.
214 public TcpClient TcpClient
245 /// Gets a value indicating whether TCP Client is Cpen
247 public override bool IsOpen
251 if (this.client == null)
256 return this.client.Connected;
261 /// Validates the certificates!<br/>
263 /// <param name="sender">The sender-object.</param>
264 /// <param name="certificate">The used certificate.</param>
265 /// <param name="chain">The certificate chain.</param>
266 /// <param name="sslValidationErrors">An enum, which lists all the errors from the .NET certificate check.</param>
267 /// <returns></returns>
268 private bool DefaultCertificateValidator(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslValidationErrors)
270 return (sslValidationErrors == SslPolicyErrors.None);
274 /// Connects to the host and starts the routine, which sets up the TLS
276 public override void Open()
280 throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected");
283 if (string.IsNullOrEmpty(host))
285 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host");
290 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port");
298 if (timeout == 0) // no timeout -> infinite
300 client.Connect(host, port);
302 else // we have a timeout -> use it
304 ConnectHelper hlp = new ConnectHelper(client);
305 IAsyncResult asyncres = client.BeginConnect(host, port, new AsyncCallback(ConnectCallback), hlp);
306 bool bConnected = asyncres.AsyncWaitHandle.WaitOne(timeout) && client.Connected;
311 if (hlp.CallbackDone)
313 asyncres.AsyncWaitHandle.Close();
318 hlp.DoCleanup = true;
322 throw new TTransportException(TTransportException.ExceptionType.TimedOut, "Connect timed out");
330 /// Creates a TLS-stream and lays it over the existing socket
332 public void setupTLS()
334 RemoteCertificateValidationCallback validator = this.certValidator ?? DefaultCertificateValidator;
336 if (this.localCertificateSelectionCallback != null)
338 this.secureStream = new SslStream(
339 this.client.GetStream(),
342 this.localCertificateSelectionCallback
347 this.secureStream = new SslStream(
348 this.client.GetStream(),
358 // Server authentication
359 this.secureStream.AuthenticateAsServer(this.certificate, this.certValidator != null, sslProtocols, true);
363 // Client authentication
364 X509CertificateCollection certs = certificate != null ? new X509CertificateCollection { certificate } : new X509CertificateCollection();
365 this.secureStream.AuthenticateAsClient(host, certs, sslProtocols, true);
374 inputStream = this.secureStream;
375 outputStream = this.secureStream;
378 static void ConnectCallback(IAsyncResult asyncres)
380 ConnectHelper hlp = asyncres.AsyncState as ConnectHelper;
383 hlp.CallbackDone = true;
387 if (hlp.Client.Client != null)
388 hlp.Client.EndConnect(asyncres);
399 asyncres.AsyncWaitHandle.Close();
401 catch (Exception) { }
405 if (hlp.Client is IDisposable)
406 ((IDisposable)hlp.Client).Dispose();
408 catch (Exception) { }
414 private class ConnectHelper
416 public object Mutex = new object();
417 public bool DoCleanup = false;
418 public bool CallbackDone = false;
419 public TcpClient Client;
420 public ConnectHelper(TcpClient client)
427 /// Closes the SSL Socket
429 public override void Close()
432 if (this.client != null)
438 if (this.secureStream != null)
440 this.secureStream.Close();
441 this.secureStream = null;