blob: 06286dc8b502580f6b73962b88f42f6a12ad0a10 [file] [log] [blame]
Jens Geyerc1d79432014-04-22 22:52:43 +02001/**
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
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
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
17 * under the License.
18 */
19
20using System;
21using System.Net.Security;
22using System.Net.Sockets;
23using System.Security.Authentication;
24using System.Security.Cryptography.X509Certificates;
25
26namespace Thrift.Transport
27{
Jens Geyerd5436f52014-10-03 19:50:38 +020028 /// <summary>
29 /// SSL Socket Wrapper class
30 /// </summary>
31 public class TTLSSocket : TStreamTransport
32 {
33 /// <summary>
34 /// Internal TCP Client
35 /// </summary>
Jens Geyer178b8132015-09-30 23:16:45 +020036 private TcpClient client;
Jens Geyerc1d79432014-04-22 22:52:43 +020037
Jens Geyerd5436f52014-10-03 19:50:38 +020038 /// <summary>
39 /// The host
40 /// </summary>
Jens Geyer178b8132015-09-30 23:16:45 +020041 private string host;
Jens Geyerc1d79432014-04-22 22:52:43 +020042
Jens Geyerd5436f52014-10-03 19:50:38 +020043 /// <summary>
44 /// The port
45 /// </summary>
Jens Geyer178b8132015-09-30 23:16:45 +020046 private int port;
Jens Geyerc1d79432014-04-22 22:52:43 +020047
Jens Geyerd5436f52014-10-03 19:50:38 +020048 /// <summary>
49 /// The timeout for the connection
50 /// </summary>
Jens Geyer178b8132015-09-30 23:16:45 +020051 private int timeout;
Jens Geyerc1d79432014-04-22 22:52:43 +020052
Jens Geyerd5436f52014-10-03 19:50:38 +020053 /// <summary>
54 /// Internal SSL Stream for IO
55 /// </summary>
Jens Geyer178b8132015-09-30 23:16:45 +020056 private SslStream secureStream;
Jens Geyerc1d79432014-04-22 22:52:43 +020057
Jens Geyerd5436f52014-10-03 19:50:38 +020058 /// <summary>
59 /// Defines wheter or not this socket is a server socket<br/>
60 /// This is used for the TLS-authentication
61 /// </summary>
Jens Geyer178b8132015-09-30 23:16:45 +020062 private bool isServer;
Jens Geyerc1d79432014-04-22 22:52:43 +020063
Jens Geyerd5436f52014-10-03 19:50:38 +020064 /// <summary>
65 /// The certificate
66 /// </summary>
Jens Geyer178b8132015-09-30 23:16:45 +020067 private X509Certificate certificate;
Jens Geyerc1d79432014-04-22 22:52:43 +020068
Jens Geyerd5436f52014-10-03 19:50:38 +020069 /// <summary>
70 /// User defined certificate validator.
71 /// </summary>
Jens Geyer178b8132015-09-30 23:16:45 +020072 private RemoteCertificateValidationCallback certValidator;
Jens Geyer7b11fec2014-06-05 22:03:19 +020073
Jens Geyerd5436f52014-10-03 19:50:38 +020074 /// <summary>
Jens Geyer1dc26532015-04-05 19:13:29 +020075 /// The function to determine which certificate to use.
76 /// </summary>
77 private LocalCertificateSelectionCallback localCertificateSelectionCallback;
78
79 /// <summary>
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +090080 /// The SslProtocols value that represents the protocol used for authentication.SSL protocols to be used.
81 /// </summary>
82 private readonly SslProtocols sslProtocols;
83
84 /// <summary>
Jens Geyerd5436f52014-10-03 19:50:38 +020085 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
86 /// </summary>
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>
Jens Geyeraf577242015-03-30 23:44:51 +020090 /// <param name="certValidator">User defined cert validator.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +020091 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +090092 /// <param name="sslProtocols">The SslProtocols value that represents the protocol used for authentication.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +020093 public TTLSSocket(
94 TcpClient client,
95 X509Certificate certificate,
96 bool isServer = false,
97 RemoteCertificateValidationCallback certValidator = null,
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +090098 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)
Jens Geyerd5436f52014-10-03 19:50:38 +0200101 {
102 this.client = client;
103 this.certificate = certificate;
Jens Geyeraf577242015-03-30 23:44:51 +0200104 this.certValidator = certValidator;
Jens Geyer1dc26532015-04-05 19:13:29 +0200105 this.localCertificateSelectionCallback = localCertificateSelectionCallback;
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900106 this.sslProtocols = sslProtocols;
Jens Geyerd5436f52014-10-03 19:50:38 +0200107 this.isServer = isServer;
Jens Geyer178b8132015-09-30 23:16:45 +0200108 if (isServer && certificate == null)
109 {
110 throw new ArgumentException("TTLSSocket needs certificate to be used for server", "certificate");
111 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200112
Jens Geyerd5436f52014-10-03 19:50:38 +0200113 if (IsOpen)
114 {
115 base.inputStream = client.GetStream();
116 base.outputStream = client.GetStream();
117 }
118 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200119
Jens Geyerd5436f52014-10-03 19:50:38 +0200120 /// <summary>
121 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
122 /// </summary>
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>
Jens Geyer1dc26532015-04-05 19:13:29 +0200127 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900128 /// <param name="sslProtocols">The SslProtocols value that represents the protocol used for authentication.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +0200129 public TTLSSocket(
130 string host,
131 int port,
132 string certificatePath,
133 RemoteCertificateValidationCallback certValidator = null,
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900134 LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
135 SslProtocols sslProtocols = SslProtocols.Tls)
136 : this(host, port, 0, X509Certificate.CreateFromCertFile(certificatePath), certValidator, localCertificateSelectionCallback, sslProtocols)
Jens Geyerd5436f52014-10-03 19:50:38 +0200137 {
138 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200139
Jens Geyerd5436f52014-10-03 19:50:38 +0200140 /// <summary>
141 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
142 /// </summary>
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>
Jens Geyer1dc26532015-04-05 19:13:29 +0200147 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900148 /// <param name="sslProtocols">The SslProtocols value that represents the protocol used for authentication.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +0200149 public TTLSSocket(
150 string host,
151 int port,
Jens Geyer178b8132015-09-30 23:16:45 +0200152 X509Certificate certificate = null,
Jens Geyer1dc26532015-04-05 19:13:29 +0200153 RemoteCertificateValidationCallback certValidator = null,
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900154 LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
155 SslProtocols sslProtocols = SslProtocols.Tls)
156 : this(host, port, 0, certificate, certValidator, localCertificateSelectionCallback, sslProtocols)
Jens Geyerd5436f52014-10-03 19:50:38 +0200157 {
158 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200159
Jens Geyerd5436f52014-10-03 19:50:38 +0200160 /// <summary>
161 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
162 /// </summary>
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>
Jens Geyer1dc26532015-04-05 19:13:29 +0200168 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900169 /// <param name="sslProtocols">The SslProtocols value that represents the protocol used for authentication.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +0200170 public TTLSSocket(
171 string host,
172 int port,
173 int timeout,
174 X509Certificate certificate,
175 RemoteCertificateValidationCallback certValidator = null,
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900176 LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
177 SslProtocols sslProtocols = SslProtocols.Tls)
Jens Geyerd5436f52014-10-03 19:50:38 +0200178 {
179 this.host = host;
180 this.port = port;
181 this.timeout = timeout;
182 this.certificate = certificate;
183 this.certValidator = certValidator;
Jens Geyer1dc26532015-04-05 19:13:29 +0200184 this.localCertificateSelectionCallback = localCertificateSelectionCallback;
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900185 this.sslProtocols = sslProtocols;
Jens Geyerc1d79432014-04-22 22:52:43 +0200186
Jens Geyerd5436f52014-10-03 19:50:38 +0200187 InitSocket();
188 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200189
Jens Geyerd5436f52014-10-03 19:50:38 +0200190 /// <summary>
191 /// Creates the TcpClient and sets the timeouts
192 /// </summary>
193 private void InitSocket()
194 {
Jens Geyerfeea4782017-01-28 19:53:28 +0100195 client = TSocketVersionizer.CreateTcpClient();
Jens Geyerd5436f52014-10-03 19:50:38 +0200196 client.ReceiveTimeout = client.SendTimeout = timeout;
197 client.Client.NoDelay = true;
198 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200199
Jens Geyerd5436f52014-10-03 19:50:38 +0200200 /// <summary>
201 /// Sets Send / Recv Timeout for IO
202 /// </summary>
203 public int Timeout
204 {
205 set
206 {
207 this.client.ReceiveTimeout = this.client.SendTimeout = this.timeout = value;
208 }
209 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200210
Jens Geyerd5436f52014-10-03 19:50:38 +0200211 /// <summary>
212 /// Gets the TCP client.
213 /// </summary>
214 public TcpClient TcpClient
215 {
216 get
217 {
218 return client;
219 }
220 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200221
Jens Geyerd5436f52014-10-03 19:50:38 +0200222 /// <summary>
223 /// Gets the host.
224 /// </summary>
225 public string Host
226 {
227 get
228 {
229 return host;
230 }
231 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200232
Jens Geyerd5436f52014-10-03 19:50:38 +0200233 /// <summary>
234 /// Gets the port.
235 /// </summary>
236 public int Port
237 {
238 get
239 {
240 return port;
241 }
242 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200243
Jens Geyerd5436f52014-10-03 19:50:38 +0200244 /// <summary>
245 /// Gets a value indicating whether TCP Client is Cpen
246 /// </summary>
247 public override bool IsOpen
248 {
249 get
250 {
251 if (this.client == null)
252 {
253 return false;
254 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200255
Jens Geyerd5436f52014-10-03 19:50:38 +0200256 return this.client.Connected;
257 }
258 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200259
Jens Geyerd5436f52014-10-03 19:50:38 +0200260 /// <summary>
261 /// Validates the certificates!<br/>
262 /// </summary>
263 /// <param name="sender">The sender-object.</param>
264 /// <param name="certificate">The used certificate.</param>
265 /// <param name="chain">The certificate chain.</param>
Christian Weiss8fb719e2018-03-30 21:26:04 +0200266 /// <param name="sslValidationErrors">An enum, which lists all the errors from the .NET certificate check.</param>
Jens Geyerd5436f52014-10-03 19:50:38 +0200267 /// <returns></returns>
Jens Geyer1dc26532015-04-05 19:13:29 +0200268 private bool DefaultCertificateValidator(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslValidationErrors)
Jens Geyerd5436f52014-10-03 19:50:38 +0200269 {
270 return (sslValidationErrors == SslPolicyErrors.None);
271 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200272
Jens Geyerd5436f52014-10-03 19:50:38 +0200273 /// <summary>
274 /// Connects to the host and starts the routine, which sets up the TLS
275 /// </summary>
276 public override void Open()
277 {
278 if (IsOpen)
279 {
280 throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected");
281 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200282
Christian Weiss8fb719e2018-03-30 21:26:04 +0200283 if (string.IsNullOrEmpty(host))
Jens Geyerd5436f52014-10-03 19:50:38 +0200284 {
285 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host");
286 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200287
Jens Geyerd5436f52014-10-03 19:50:38 +0200288 if (port <= 0)
289 {
290 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port");
291 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200292
Jens Geyerd5436f52014-10-03 19:50:38 +0200293 if (client == null)
294 {
295 InitSocket();
296 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200297
Christoph Herold1ad08ca2019-01-15 10:23:51 +0100298 if (timeout == 0) // no timeout -> infinite
299 {
300 client.Connect(host, port);
301 }
302 else // we have a timeout -> use it
303 {
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;
307 if (!bConnected)
308 {
309 lock (hlp.Mutex)
310 {
311 if (hlp.CallbackDone)
312 {
313 asyncres.AsyncWaitHandle.Close();
314 client.Close();
315 }
316 else
317 {
318 hlp.DoCleanup = true;
319 client = null;
320 }
321 }
322 throw new TTransportException(TTransportException.ExceptionType.TimedOut, "Connect timed out");
323 }
324 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200325
Jens Geyerd5436f52014-10-03 19:50:38 +0200326 setupTLS();
327 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200328
Jens Geyerd5436f52014-10-03 19:50:38 +0200329 /// <summary>
330 /// Creates a TLS-stream and lays it over the existing socket
331 /// </summary>
332 public void setupTLS()
333 {
Jens Geyer1dc26532015-04-05 19:13:29 +0200334 RemoteCertificateValidationCallback validator = this.certValidator ?? DefaultCertificateValidator;
Adam Connelly0c0649d2015-12-26 15:55:05 +0000335
Christian Weiss8fb719e2018-03-30 21:26:04 +0200336 if (this.localCertificateSelectionCallback != null)
Jens Geyerd5436f52014-10-03 19:50:38 +0200337 {
Jens Geyer1dc26532015-04-05 19:13:29 +0200338 this.secureStream = new SslStream(
339 this.client.GetStream(),
340 false,
341 validator,
342 this.localCertificateSelectionCallback
343 );
Jens Geyerd5436f52014-10-03 19:50:38 +0200344 }
345 else
346 {
Jens Geyer1dc26532015-04-05 19:13:29 +0200347 this.secureStream = new SslStream(
348 this.client.GetStream(),
349 false,
350 validator
351 );
352 }
Adam Connelly0c0649d2015-12-26 15:55:05 +0000353
Jens Geyer1dc26532015-04-05 19:13:29 +0200354 try
355 {
356 if (isServer)
357 {
358 // Server authentication
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900359 this.secureStream.AuthenticateAsServer(this.certificate, this.certValidator != null, sslProtocols, true);
Jens Geyer1dc26532015-04-05 19:13:29 +0200360 }
361 else
362 {
363 // Client authentication
Christian Weiss8fb719e2018-03-30 21:26:04 +0200364 X509CertificateCollection certs = certificate != null ? new X509CertificateCollection { certificate } : new X509CertificateCollection();
Nobuaki Sukegawa474ddbd2016-02-17 23:44:27 +0900365 this.secureStream.AuthenticateAsClient(host, certs, sslProtocols, true);
Jens Geyer1dc26532015-04-05 19:13:29 +0200366 }
367 }
368 catch (Exception)
369 {
370 this.Close();
371 throw;
Jens Geyerd5436f52014-10-03 19:50:38 +0200372 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200373
Jens Geyerd5436f52014-10-03 19:50:38 +0200374 inputStream = this.secureStream;
375 outputStream = this.secureStream;
376 }
Christoph Herold1ad08ca2019-01-15 10:23:51 +0100377
378 static void ConnectCallback(IAsyncResult asyncres)
379 {
380 ConnectHelper hlp = asyncres.AsyncState as ConnectHelper;
381 lock (hlp.Mutex)
382 {
383 hlp.CallbackDone = true;
384
385 try
386 {
387 if (hlp.Client.Client != null)
388 hlp.Client.EndConnect(asyncres);
389 }
390 catch (Exception)
391 {
392 // catch that away
393 }
394
395 if (hlp.DoCleanup)
396 {
397 try
398 {
399 asyncres.AsyncWaitHandle.Close();
400 }
401 catch (Exception) { }
402
403 try
404 {
405 if (hlp.Client is IDisposable)
406 ((IDisposable)hlp.Client).Dispose();
407 }
408 catch (Exception) { }
409 hlp.Client = null;
410 }
411 }
412 }
413
414 private class ConnectHelper
415 {
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)
421 {
422 Client = client;
423 }
424 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200425
Jens Geyerd5436f52014-10-03 19:50:38 +0200426 /// <summary>
427 /// Closes the SSL Socket
428 /// </summary>
429 public override void Close()
430 {
431 base.Close();
432 if (this.client != null)
433 {
434 this.client.Close();
435 this.client = null;
436 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200437
Jens Geyerd5436f52014-10-03 19:50:38 +0200438 if (this.secureStream != null)
439 {
440 this.secureStream.Close();
441 this.secureStream = null;
442 }
443 }
444 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200445}