blob: 565255607ea1cc633985ff2bd2cd12f39aea74db [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>
36 private TcpClient client = null;
Jens Geyerc1d79432014-04-22 22:52:43 +020037
Jens Geyerd5436f52014-10-03 19:50:38 +020038 /// <summary>
39 /// The host
40 /// </summary>
41 private string host = null;
Jens Geyerc1d79432014-04-22 22:52:43 +020042
Jens Geyerd5436f52014-10-03 19:50:38 +020043 /// <summary>
44 /// The port
45 /// </summary>
46 private int port = 0;
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>
51 private int timeout = 0;
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>
56 private SslStream secureStream = null;
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>
62 private bool isServer = false;
Jens Geyerc1d79432014-04-22 22:52:43 +020063
Jens Geyerd5436f52014-10-03 19:50:38 +020064 /// <summary>
65 /// The certificate
66 /// </summary>
67 private X509Certificate certificate = null;
Jens Geyerc1d79432014-04-22 22:52:43 +020068
Jens Geyerd5436f52014-10-03 19:50:38 +020069 /// <summary>
70 /// User defined certificate validator.
71 /// </summary>
72 private RemoteCertificateValidationCallback certValidator = null;
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 Geyerc1d79432014-04-22 22:52:43 +020099
Jens Geyerd5436f52014-10-03 19:50:38 +0200100 if (IsOpen)
101 {
102 base.inputStream = client.GetStream();
103 base.outputStream = client.GetStream();
104 }
105 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200106
Jens Geyerd5436f52014-10-03 19:50:38 +0200107 /// <summary>
108 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
109 /// </summary>
110 /// <param name="host">The host, where the socket should connect to.</param>
111 /// <param name="port">The port.</param>
112 /// <param name="certificatePath">The certificate path.</param>
113 /// <param name="certValidator">User defined cert validator.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +0200114 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
115 public TTLSSocket(
116 string host,
117 int port,
118 string certificatePath,
119 RemoteCertificateValidationCallback certValidator = null,
120 LocalCertificateSelectionCallback localCertificateSelectionCallback = null)
121 : this(host, port, 0, X509Certificate.CreateFromCertFile(certificatePath), certValidator, localCertificateSelectionCallback)
Jens Geyerd5436f52014-10-03 19:50:38 +0200122 {
123 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200124
Jens Geyerd5436f52014-10-03 19:50:38 +0200125 /// <summary>
126 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
127 /// </summary>
128 /// <param name="host">The host, where the socket should connect to.</param>
129 /// <param name="port">The port.</param>
130 /// <param name="certificate">The certificate.</param>
131 /// <param name="certValidator">User defined cert validator.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +0200132 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
133 public TTLSSocket(
134 string host,
135 int port,
136 X509Certificate certificate,
137 RemoteCertificateValidationCallback certValidator = null,
138 LocalCertificateSelectionCallback localCertificateSelectionCallback = null)
139 : this(host, port, 0, certificate, certValidator, localCertificateSelectionCallback)
Jens Geyerd5436f52014-10-03 19:50:38 +0200140 {
141 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200142
Jens Geyerd5436f52014-10-03 19:50:38 +0200143 /// <summary>
144 /// Initializes a new instance of the <see cref="TTLSSocket"/> class.
145 /// </summary>
146 /// <param name="host">The host, where the socket should connect to.</param>
147 /// <param name="port">The port.</param>
148 /// <param name="timeout">The timeout.</param>
149 /// <param name="certificate">The certificate.</param>
150 /// <param name="certValidator">User defined cert validator.</param>
Jens Geyer1dc26532015-04-05 19:13:29 +0200151 /// <param name="localCertificateSelectionCallback">The callback to select which certificate to use.</param>
152 public TTLSSocket(
153 string host,
154 int port,
155 int timeout,
156 X509Certificate certificate,
157 RemoteCertificateValidationCallback certValidator = null,
158 LocalCertificateSelectionCallback localCertificateSelectionCallback = null)
Jens Geyerd5436f52014-10-03 19:50:38 +0200159 {
160 this.host = host;
161 this.port = port;
162 this.timeout = timeout;
163 this.certificate = certificate;
164 this.certValidator = certValidator;
Jens Geyer1dc26532015-04-05 19:13:29 +0200165 this.localCertificateSelectionCallback = localCertificateSelectionCallback;
Jens Geyerc1d79432014-04-22 22:52:43 +0200166
Jens Geyerd5436f52014-10-03 19:50:38 +0200167 InitSocket();
168 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200169
Jens Geyerd5436f52014-10-03 19:50:38 +0200170 /// <summary>
171 /// Creates the TcpClient and sets the timeouts
172 /// </summary>
173 private void InitSocket()
174 {
175 this.client = new TcpClient();
176 client.ReceiveTimeout = client.SendTimeout = timeout;
177 client.Client.NoDelay = true;
178 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200179
Jens Geyerd5436f52014-10-03 19:50:38 +0200180 /// <summary>
181 /// Sets Send / Recv Timeout for IO
182 /// </summary>
183 public int Timeout
184 {
185 set
186 {
187 this.client.ReceiveTimeout = this.client.SendTimeout = this.timeout = value;
188 }
189 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200190
Jens Geyerd5436f52014-10-03 19:50:38 +0200191 /// <summary>
192 /// Gets the TCP client.
193 /// </summary>
194 public TcpClient TcpClient
195 {
196 get
197 {
198 return client;
199 }
200 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200201
Jens Geyerd5436f52014-10-03 19:50:38 +0200202 /// <summary>
203 /// Gets the host.
204 /// </summary>
205 public string Host
206 {
207 get
208 {
209 return host;
210 }
211 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200212
Jens Geyerd5436f52014-10-03 19:50:38 +0200213 /// <summary>
214 /// Gets the port.
215 /// </summary>
216 public int Port
217 {
218 get
219 {
220 return port;
221 }
222 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200223
Jens Geyerd5436f52014-10-03 19:50:38 +0200224 /// <summary>
225 /// Gets a value indicating whether TCP Client is Cpen
226 /// </summary>
227 public override bool IsOpen
228 {
229 get
230 {
231 if (this.client == null)
232 {
233 return false;
234 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200235
Jens Geyerd5436f52014-10-03 19:50:38 +0200236 return this.client.Connected;
237 }
238 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200239
Jens Geyerd5436f52014-10-03 19:50:38 +0200240 /// <summary>
241 /// Validates the certificates!<br/>
242 /// </summary>
243 /// <param name="sender">The sender-object.</param>
244 /// <param name="certificate">The used certificate.</param>
245 /// <param name="chain">The certificate chain.</param>
246 /// <param name="sslPolicyErrors">An enum, which lists all the errors from the .NET certificate check.</param>
247 /// <returns></returns>
Jens Geyer1dc26532015-04-05 19:13:29 +0200248 private bool DefaultCertificateValidator(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslValidationErrors)
Jens Geyerd5436f52014-10-03 19:50:38 +0200249 {
250 return (sslValidationErrors == SslPolicyErrors.None);
251 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200252
Jens Geyerd5436f52014-10-03 19:50:38 +0200253 /// <summary>
254 /// Connects to the host and starts the routine, which sets up the TLS
255 /// </summary>
256 public override void Open()
257 {
258 if (IsOpen)
259 {
260 throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected");
261 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200262
Jens Geyerd5436f52014-10-03 19:50:38 +0200263 if (String.IsNullOrEmpty(host))
264 {
265 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host");
266 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200267
Jens Geyerd5436f52014-10-03 19:50:38 +0200268 if (port <= 0)
269 {
270 throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port");
271 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200272
Jens Geyerd5436f52014-10-03 19:50:38 +0200273 if (client == null)
274 {
275 InitSocket();
276 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200277
Jens Geyerd5436f52014-10-03 19:50:38 +0200278 client.Connect(host, port);
Jens Geyerc1d79432014-04-22 22:52:43 +0200279
Jens Geyerd5436f52014-10-03 19:50:38 +0200280 setupTLS();
281 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200282
Jens Geyerd5436f52014-10-03 19:50:38 +0200283 /// <summary>
284 /// Creates a TLS-stream and lays it over the existing socket
285 /// </summary>
286 public void setupTLS()
287 {
Jens Geyer1dc26532015-04-05 19:13:29 +0200288 RemoteCertificateValidationCallback validator = this.certValidator ?? DefaultCertificateValidator;
289
290 if( this.localCertificateSelectionCallback != null)
Jens Geyerd5436f52014-10-03 19:50:38 +0200291 {
Jens Geyer1dc26532015-04-05 19:13:29 +0200292 this.secureStream = new SslStream(
293 this.client.GetStream(),
294 false,
295 validator,
296 this.localCertificateSelectionCallback
297 );
Jens Geyerd5436f52014-10-03 19:50:38 +0200298 }
299 else
300 {
Jens Geyer1dc26532015-04-05 19:13:29 +0200301 this.secureStream = new SslStream(
302 this.client.GetStream(),
303 false,
304 validator
305 );
306 }
307
308 try
309 {
310 if (isServer)
311 {
312 // Server authentication
313 this.secureStream.AuthenticateAsServer(this.certificate, this.certValidator != null, SslProtocols.Tls, true);
314 }
315 else
316 {
317 // Client authentication
318 this.secureStream.AuthenticateAsClient(host, new X509CertificateCollection { certificate }, SslProtocols.Tls, true);
319 }
320 }
321 catch (Exception)
322 {
323 this.Close();
324 throw;
Jens Geyerd5436f52014-10-03 19:50:38 +0200325 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200326
Jens Geyerd5436f52014-10-03 19:50:38 +0200327 inputStream = this.secureStream;
328 outputStream = this.secureStream;
329 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200330
Jens Geyerd5436f52014-10-03 19:50:38 +0200331 /// <summary>
332 /// Closes the SSL Socket
333 /// </summary>
334 public override void Close()
335 {
336 base.Close();
337 if (this.client != null)
338 {
339 this.client.Close();
340 this.client = null;
341 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200342
Jens Geyerd5436f52014-10-03 19:50:38 +0200343 if (this.secureStream != null)
344 {
345 this.secureStream.Close();
346 this.secureStream = null;
347 }
348 }
349 }
Jens Geyerc1d79432014-04-22 22:52:43 +0200350}