/**
* 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
{
///
/// SSL Socket Wrapper class
///
public class TTLSSocket : TStreamTransport
{
///
/// Internal TCP Client
///
private TcpClient client;
///
/// The host
///
private string host;
///
/// The port
///
private int port;
///
/// The timeout for the connection
///
private int timeout;
///
/// Internal SSL Stream for IO
///
private SslStream secureStream;
///
/// Defines wheter or not this socket is a server socket
/// This is used for the TLS-authentication
///
private bool isServer;
///
/// The certificate
///
private X509Certificate certificate;
///
/// User defined certificate validator.
///
private RemoteCertificateValidationCallback certValidator;
///
/// The function to determine which certificate to use.
///
private LocalCertificateSelectionCallback localCertificateSelectionCallback;
///
/// The SslProtocols value that represents the protocol used for authentication.SSL protocols to be used.
///
private readonly SslProtocols sslProtocols;
///
/// Initializes a new instance of the class.
///
/// An already created TCP-client
/// The certificate.
/// if set to true [is server].
/// User defined cert validator.
/// The callback to select which certificate to use.
/// The SslProtocols value that represents the protocol used for authentication.
public TTLSSocket(
TcpClient client,
X509Certificate certificate,
bool isServer = false,
RemoteCertificateValidationCallback certValidator = null,
LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
// TODO: Enable Tls11 and Tls12 (TLS 1.1 and 1.2) by default once we start using .NET 4.5+.
SslProtocols sslProtocols = SslProtocols.Tls)
{
this.client = client;
this.certificate = certificate;
this.certValidator = certValidator;
this.localCertificateSelectionCallback = localCertificateSelectionCallback;
this.sslProtocols = sslProtocols;
this.isServer = isServer;
if (isServer && certificate == null)
{
throw new ArgumentException("TTLSSocket needs certificate to be used for server", "certificate");
}
if (IsOpen)
{
base.inputStream = client.GetStream();
base.outputStream = client.GetStream();
}
}
///
/// Initializes a new instance of the class.
///
/// The host, where the socket should connect to.
/// The port.
/// The certificate path.
/// User defined cert validator.
/// The callback to select which certificate to use.
/// The SslProtocols value that represents the protocol used for authentication.
public TTLSSocket(
string host,
int port,
string certificatePath,
RemoteCertificateValidationCallback certValidator = null,
LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
SslProtocols sslProtocols = SslProtocols.Tls)
: this(host, port, 0, X509Certificate.CreateFromCertFile(certificatePath), certValidator, localCertificateSelectionCallback, sslProtocols)
{
}
///
/// Initializes a new instance of the class.
///
/// The host, where the socket should connect to.
/// The port.
/// The certificate.
/// User defined cert validator.
/// The callback to select which certificate to use.
/// The SslProtocols value that represents the protocol used for authentication.
public TTLSSocket(
string host,
int port,
X509Certificate certificate = null,
RemoteCertificateValidationCallback certValidator = null,
LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
SslProtocols sslProtocols = SslProtocols.Tls)
: this(host, port, 0, certificate, certValidator, localCertificateSelectionCallback, sslProtocols)
{
}
///
/// Initializes a new instance of the class.
///
/// The host, where the socket should connect to.
/// The port.
/// The timeout.
/// The certificate.
/// User defined cert validator.
/// The callback to select which certificate to use.
/// The SslProtocols value that represents the protocol used for authentication.
public TTLSSocket(
string host,
int port,
int timeout,
X509Certificate certificate,
RemoteCertificateValidationCallback certValidator = null,
LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
SslProtocols sslProtocols = SslProtocols.Tls)
{
this.host = host;
this.port = port;
this.timeout = timeout;
this.certificate = certificate;
this.certValidator = certValidator;
this.localCertificateSelectionCallback = localCertificateSelectionCallback;
this.sslProtocols = sslProtocols;
InitSocket();
}
///
/// Creates the TcpClient and sets the timeouts
///
private void InitSocket()
{
client = TSocketVersionizer.CreateTcpClient();
client.ReceiveTimeout = client.SendTimeout = timeout;
client.Client.NoDelay = true;
}
///
/// Sets Send / Recv Timeout for IO
///
public int Timeout
{
set
{
this.client.ReceiveTimeout = this.client.SendTimeout = this.timeout = value;
}
}
///
/// Gets the TCP client.
///
public TcpClient TcpClient
{
get
{
return client;
}
}
///
/// Gets the host.
///
public string Host
{
get
{
return host;
}
}
///
/// Gets the port.
///
public int Port
{
get
{
return port;
}
}
///
/// Gets a value indicating whether TCP Client is Cpen
///
public override bool IsOpen
{
get
{
if (this.client == null)
{
return false;
}
return this.client.Connected;
}
}
///
/// Validates the certificates!
///
/// The sender-object.
/// The used certificate.
/// The certificate chain.
/// An enum, which lists all the errors from the .NET certificate check.
///
private bool DefaultCertificateValidator(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslValidationErrors)
{
return (sslValidationErrors == SslPolicyErrors.None);
}
///
/// Connects to the host and starts the routine, which sets up the TLS
///
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();
}
///
/// Creates a TLS-stream and lays it over the existing socket
///
public void setupTLS()
{
RemoteCertificateValidationCallback validator = this.certValidator ?? DefaultCertificateValidator;
if (this.localCertificateSelectionCallback != null)
{
this.secureStream = new SslStream(
this.client.GetStream(),
false,
validator,
this.localCertificateSelectionCallback
);
}
else
{
this.secureStream = new SslStream(
this.client.GetStream(),
false,
validator
);
}
try
{
if (isServer)
{
// Server authentication
this.secureStream.AuthenticateAsServer(this.certificate, this.certValidator != null, sslProtocols, true);
}
else
{
// Client authentication
X509CertificateCollection certs = certificate != null ? new X509CertificateCollection { certificate } : new X509CertificateCollection();
this.secureStream.AuthenticateAsClient(host, certs, sslProtocols, true);
}
}
catch (Exception)
{
this.Close();
throw;
}
inputStream = this.secureStream;
outputStream = this.secureStream;
}
///
/// Closes the SSL Socket
///
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;
}
}
}
}