blob: 833b792c16b4733f29e051256c7f3f476c264353 [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>
Jens Geyerd5436f52014-10-03 19:50:38 +020080 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
81 /// </summary>
82 /// <param name="client">An already created TCP-client</param>
83 /// <param name="certificate">The certificate.</param>
84 /// <param name="isServer">if set to <c>true</c> [is server].</param>
Jens Geyeraf577242015-03-30 23:44:51 +020085 /// <param name="certValidator">User defined cert validator.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +020086 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
87 public TTLSSocket(
88 TcpClient client,
89 X509Certificate certificate,
90 bool isServer = false,
91 RemoteCertificateValidationCallback certValidator = null,
92 LocalCertificateSelectionCallback localCertificateSelectionCallback = null)
Jens Geyerd5436f52014-10-03 19:50:38 +020093 {
94 this.client = client;
95 this.certificate = certificate;
Jens Geyeraf577242015-03-30 23:44:51 +020096 this.certValidator = certValidator;
Jens Geyer1dc26532015-04-05 19:13:29 +020097 this.localCertificateSelectionCallback = localCertificateSelectionCallback;
Jens Geyerd5436f52014-10-03 19:50:38 +020098 this.isServer = isServer;
Jens Geyer178b8132015-09-30 23:16:45 +020099 if (isServer && certificate == null)
100 {
101 throw new ArgumentException("TTLSSocket needs certificate to be used for server", "certificate");
102 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200103
Jens Geyerd5436f52014-10-03 19:50:38 +0200104 if (IsOpen)
105 {
106 base.inputStream = client.GetStream();
107 base.outputStream = client.GetStream();
108 }
109 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200110
Jens Geyerd5436f52014-10-03 19:50:38 +0200111 /// <summary>
112 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
113 /// </summary>
114 /// <param name="host">The host, where the socket should connect to.</param>
115 /// <param name="port">The port.</param>
116 /// <param name="certificatePath">The certificate path.</param>
117 /// <param name="certValidator">User defined cert validator.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +0200118 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
119 public TTLSSocket(
120 string host,
121 int port,
122 string certificatePath,
123 RemoteCertificateValidationCallback certValidator = null,
124 LocalCertificateSelectionCallback localCertificateSelectionCallback = null)
125 : this(host, port, 0, X509Certificate.CreateFromCertFile(certificatePath), certValidator, localCertificateSelectionCallback)
Jens Geyerd5436f52014-10-03 19:50:38 +0200126 {
127 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200128
Jens Geyerd5436f52014-10-03 19:50:38 +0200129 /// <summary>
130 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
131 /// </summary>
132 /// <param name="host">The host, where the socket should connect to.</param>
133 /// <param name="port">The port.</param>
134 /// <param name="certificate">The certificate.</param>
135 /// <param name="certValidator">User defined cert validator.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +0200136 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
137 public TTLSSocket(
138 string host,
139 int port,
Jens Geyer178b8132015-09-30 23:16:45 +0200140 X509Certificate certificate = null,
Jens Geyer1dc26532015-04-05 19:13:29 +0200141 RemoteCertificateValidationCallback certValidator = null,
142 LocalCertificateSelectionCallback localCertificateSelectionCallback = null)
143 : this(host, port, 0, certificate, certValidator, localCertificateSelectionCallback)
Jens Geyerd5436f52014-10-03 19:50:38 +0200144 {
145 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200146
Jens Geyerd5436f52014-10-03 19:50:38 +0200147 /// <summary>
148 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
149 /// </summary>
150 /// <param name="host">The host, where the socket should connect to.</param>
151 /// <param name="port">The port.</param>
152 /// <param name="timeout">The timeout.</param>
153 /// <param name="certificate">The certificate.</param>
154 /// <param name="certValidator">User defined cert validator.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +0200155 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
156 public TTLSSocket(
157 string host,
158 int port,
159 int timeout,
160 X509Certificate certificate,
161 RemoteCertificateValidationCallback certValidator = null,
162 LocalCertificateSelectionCallback localCertificateSelectionCallback = null)
Jens Geyerd5436f52014-10-03 19:50:38 +0200163 {
164 this.host = host;
165 this.port = port;
166 this.timeout = timeout;
167 this.certificate = certificate;
168 this.certValidator = certValidator;
Jens Geyer1dc26532015-04-05 19:13:29 +0200169 this.localCertificateSelectionCallback = localCertificateSelectionCallback;
Jens Geyerc1d79432014-04-22 22:52:43 +0200170
Jens Geyerd5436f52014-10-03 19:50:38 +0200171 InitSocket();
172 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200173
Jens Geyerd5436f52014-10-03 19:50:38 +0200174 /// <summary>
175 /// Creates the TcpClient and sets the timeouts
176 /// </summary>
177 private void InitSocket()
178 {
Jens Geyer95717c92015-04-23 22:48:13 +0200179 this.client = new TcpClient();
Jens Geyerd5436f52014-10-03 19:50:38 +0200180 client.ReceiveTimeout = client.SendTimeout = timeout;
181 client.Client.NoDelay = true;
182 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200183
Jens Geyerd5436f52014-10-03 19:50:38 +0200184 /// <summary>
185 /// Sets Send / Recv Timeout for IO
186 /// </summary>
187 public int Timeout
188 {
189 set
190 {
191 this.client.ReceiveTimeout = this.client.SendTimeout = this.timeout = value;
192 }
193 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200194
Jens Geyerd5436f52014-10-03 19:50:38 +0200195 /// <summary>
196 /// Gets the TCP client.
197 /// </summary>
198 public TcpClient TcpClient
199 {
200 get
201 {
202 return client;
203 }
204 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200205
Jens Geyerd5436f52014-10-03 19:50:38 +0200206 /// <summary>
207 /// Gets the host.
208 /// </summary>
209 public string Host
210 {
211 get
212 {
213 return host;
214 }
215 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200216
Jens Geyerd5436f52014-10-03 19:50:38 +0200217 /// <summary>
218 /// Gets the port.
219 /// </summary>
220 public int Port
221 {
222 get
223 {
224 return port;
225 }
226 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200227
Jens Geyerd5436f52014-10-03 19:50:38 +0200228 /// <summary>
229 /// Gets a value indicating whether TCP Client is Cpen
230 /// </summary>
231 public override bool IsOpen
232 {
233 get
234 {
235 if (this.client == null)
236 {
237 return false;
238 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200239
Jens Geyerd5436f52014-10-03 19:50:38 +0200240 return this.client.Connected;
241 }
242 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200243
Jens Geyerd5436f52014-10-03 19:50:38 +0200244 /// <summary>
245 /// Validates the certificates!<br/>
246 /// </summary>
247 /// <param name="sender">The sender-object.</param>
248 /// <param name="certificate">The used certificate.</param>
249 /// <param name="chain">The certificate chain.</param>
250 /// <param name="sslPolicyErrors">An enum, which lists all the errors from the .NET certificate check.</param>
251 /// <returns></returns>
Jens Geyer1dc26532015-04-05 19:13:29 +0200252 private bool DefaultCertificateValidator(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslValidationErrors)
Jens Geyerd5436f52014-10-03 19:50:38 +0200253 {
254 return (sslValidationErrors == SslPolicyErrors.None);
255 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200256
Jens Geyerd5436f52014-10-03 19:50:38 +0200257 /// <summary>
258 /// Connects to the host and starts the routine, which sets up the TLS
259 /// </summary>
260 public override void Open()
261 {
262 if (IsOpen)
263 {
264 throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected");
265 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200266
Jens Geyerd5436f52014-10-03 19:50:38 +0200267 if (String.IsNullOrEmpty(host))
268 {
269 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host");
270 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200271
Jens Geyerd5436f52014-10-03 19:50:38 +0200272 if (port <= 0)
273 {
274 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port");
275 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200276
Jens Geyerd5436f52014-10-03 19:50:38 +0200277 if (client == null)
278 {
279 InitSocket();
280 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200281
Jens Geyerd5436f52014-10-03 19:50:38 +0200282 client.Connect(host, port);
Jens Geyerc1d79432014-04-22 22:52:43 +0200283
Jens Geyerd5436f52014-10-03 19:50:38 +0200284 setupTLS();
285 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200286
Jens Geyerd5436f52014-10-03 19:50:38 +0200287 /// <summary>
288 /// Creates a TLS-stream and lays it over the existing socket
289 /// </summary>
290 public void setupTLS()
291 {
Jens Geyer1dc26532015-04-05 19:13:29 +0200292 RemoteCertificateValidationCallback validator = this.certValidator ?? DefaultCertificateValidator;
Jens Geyer95717c92015-04-23 22:48:13 +0200293
Jens Geyer1dc26532015-04-05 19:13:29 +0200294 if( this.localCertificateSelectionCallback != null)
Jens Geyerd5436f52014-10-03 19:50:38 +0200295 {
Jens Geyer1dc26532015-04-05 19:13:29 +0200296 this.secureStream = new SslStream(
297 this.client.GetStream(),
298 false,
299 validator,
300 this.localCertificateSelectionCallback
301 );
Jens Geyerd5436f52014-10-03 19:50:38 +0200302 }
303 else
304 {
Jens Geyer1dc26532015-04-05 19:13:29 +0200305 this.secureStream = new SslStream(
306 this.client.GetStream(),
307 false,
308 validator
309 );
310 }
Jens Geyer95717c92015-04-23 22:48:13 +0200311
Jens Geyer1dc26532015-04-05 19:13:29 +0200312 try
313 {
314 if (isServer)
315 {
316 // Server authentication
317 this.secureStream.AuthenticateAsServer(this.certificate, this.certValidator != null, SslProtocols.Tls, true);
318 }
319 else
320 {
321 // Client authentication
Jens Geyer178b8132015-09-30 23:16:45 +0200322 X509CertificateCollection certs = certificate != null ? new X509CertificateCollection { certificate } : new X509CertificateCollection();
323 this.secureStream.AuthenticateAsClient(host, certs, SslProtocols.Tls, true);
Jens Geyer1dc26532015-04-05 19:13:29 +0200324 }
325 }
326 catch (Exception)
327 {
328 this.Close();
329 throw;
Jens Geyerd5436f52014-10-03 19:50:38 +0200330 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200331
Jens Geyerd5436f52014-10-03 19:50:38 +0200332 inputStream = this.secureStream;
333 outputStream = this.secureStream;
334 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200335
Jens Geyerd5436f52014-10-03 19:50:38 +0200336 /// <summary>
337 /// Closes the SSL Socket
338 /// </summary>
339 public override void Close()
340 {
341 base.Close();
342 if (this.client != null)
343 {
344 this.client.Close();
345 this.client = null;
346 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200347
Jens Geyerd5436f52014-10-03 19:50:38 +0200348 if (this.secureStream != null)
349 {
350 this.secureStream.Close();
351 this.secureStream = null;
352 }
353 }
354 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200355}