Click here to monitor SSC
  • Av rating:
  • Total votes: 39
  • Total comments: 3
Matteo Slaviero

TLS/SSL and .NET Framework 4.0

28 September 2011

The Secure Socket Layer is now essential for the secure exchange of digital data, and is most generally used within the HTTPS protocol. .NET now provides the Windows Communication Foundation (WCF) to implement secure communications directly. Matteo explains the TLS/SSL protocol, and takes a hands-on approach to investigate the SslStream class to show how to  implement a secure communication channel

  1. Introduction
  2. TLS/SSL Protocol Overview
  3. TLS/SSL and the .NET Framework
  4. Using the SslStream class
    1. Handshake
    2. Validation
    3. Certificates Selection
    4. Data Exchange
  5. CipherSuites

INTRODUCTION

Since its debut in 1994, when Netscape Communication designed version 1.0 of the Secure Socket Layer specification, SSL began to emerge as the most popular protocol for the secure exchange of digital data between remote systems. Its success was so remarkable that the Internet Engineering Task Force (IETF) decided, in 1996, to adopt it as an Internet Standard under the name of Transport Layer Security (TLS).

Even though it was originally intended for protecting electronic e-commerce transactions, the growth over time of the world wide web both in terms of the number of people using it every day and the number of available services, forced the TLS/SSL protocol to be applied to very different scenarios from those envisaged at the outset. Think about technologies like FTPS, secure e-mail delivery using TLS channels, SSL VPN, and so on.

That’s not all. The growing complexity of “digital communication” doesn’t involve only the underlying technologies, but it has social impacts too. For example, Web 2.0 has enabled users to become involved in website content generation; they can upload documents and share photos or videos, they can blog or post comments on other people’s blogs, and so on. This “opening up to users” has dramatically increased information exchange, including personal and sensitive data, and it has revealed a “dark side”… sadly, more data around also means more risk that it may be used in a fraudulent manner. Protecting the data has therefore become an important requirement. At present, the HTTPS protocol, based on TLS/SSL protocol and digital certificates, is probably the most widely-adopted solution.

High level solutions for developing client/server communication systems that use the TSL/SSL protocol to protect data are available, Windows Communication Foundation (WCF) being probably the most important. However, analyzing at low level how TLS/SSL protocol works can be very useful to better understand what happens behind the scenes, whatever solution you are working on. In this article, I would like to investigate, in-depth, how the SslStream class of the .NET Framework operates, and how to use it to develop a simple client/server communication system that uses the TLS/SSL protocol to protect data exchange.

TLS/SSL PROTOCOL OVERVIEW

Why has the TLS/SSL protocol been so widely adopted? Like me, many of you have probably studied the TCP/IP protocols family and learnt that, even if an official standard protocol exists (the ISO/OSI model), TCP/IP became the “de facto” standard for all network communications due to its simplicity and its ability to work well in every situation. In my opinion, the success of the TLS/SSL protocol relies on the fact that it was designed to perfectly match the TCP/IP protocol architecture. It maintains the TCP/IP’s layer-based design principles, thus inheriting all their advantages. It maybe for this reason that protocols like S-HTTP have not had the same success.

Figure 1 shows how TLS/SSL protocol interacts with the TCP/IP protocol.

Figure 1: Comparing TCP/IP and TCP/IP with TLS/SSL.

TLS/SSL adds a new layer to the TCP/IP layers stack. As with all the other layers in the stack, the TLS/SSL protocols are independent of the protocols above and below, but the layer “speaks the same language” as the same layer on the other side of the communications channel. This design not only ensures full compatibility with all the network technologies based on TCP/IP, but it also enables them to easily “switch” to their secure versions without having to reinvent them from the ground up. So, HTTP became HTTPS without having to modify its specifications, FTP became FTPS, and so on.

To keep the data secure during the communication, TLS/SSL uses cryptographic techniques. Among the four goals of cryptography (confidentiality, integrity, authentication, and non-repudiation) TLS/SSL is able to guarantee confidentiality, integrity, and authentication. This is done principally in two steps:

  1. Authentication of the entities involved in the data exchange and negotiation of the cryptographic parameters to be used during the communication. This step uses asymmetric cryptography and X509 digital certificates.
  2. Symmetric encryption of exchanged data, and message authentication code (MAC) calculation and verification of each packet transmitted. The first assures confidentiality, the second integrity.

Referring to the TLS 1.2 specification defined in RFC 5246, the first step occurs by using a series of messages that the two communicating entities (client and server) exchange to start the secure communication. The protocol that specifies the kind of messages to be exchanged and their relative order is the Handshake Protocol . During the handshake, the client and the server agree on the TLS/SSL protocol version to adopt, they decide on the cryptographic algorithms and the relative parameters that they will use, they (optionally) authenticate each other, and finally they exchange shared secrets to use during the second step.

Table 1 summarizes the principal steps involved in the handshake.

Client Message: ClientHello =>

Exchanged data:

·     Max SSL Protocol version allowed

·     SessionID

·     Set of Cipher Suites

·     Set of Compression Methods

·     Random number

The client sends the maximum number of the SSL protocol version it understands, a session id to be used to resume the communication if needed, a set of cipher suites and compression methods that it is able to use, and a random generated number to be used during the key exchange.

Server Message: <= ServerHello

Exchanged data:

·      SSL Protocol Selected

·     SessionID

·     Selected Cipher Suite

·     Selected Compression Method

·     Random Number

The server sends the SSL protocol version selected (not exceeding the maximum supported by the client), a session id, the cipher suite and compression method selected from those suggested by the client, and a random number to be used during the key exchange.

Server Message: <= ServerCertificate

Exchanged data:

·      Server’s X509 Certificate(s)

The server sends its X509 certificate (or X509 certificates chain) to use during the key exchange to encrypt key materials and to authenticate itself.

Server Message: <= ServerKeyExchange

Exchanged data:

·      Public key to use for the key exchange algorithm (optional)

If the server’s certificate is not able to perform encryption, the server sends a public key to be used to encrypt key materials.

Server Message: <= CertificateRequest

Exchanged data:

·      None

(Optional) If client authentication is required, the server sends the CertificateRequest message to the client

Server Message: <= ServerHelloDone

Exchanged data:

·      None

End of the ServerHello.

Client Message: Certificate =>

Exchanged data:

·      Client’s X509 Certificate (optional)

If client authentication is required, the client sends to the server its X509 certificate (or X509 certificates chain).

Client Message: ClientKeyExchange =>

Exchanged data:

·      Key material encrypted with the server’s certificate

The client sends all the information needed to agree with the server the symmetric key to use during the communication. The type of information depends on the key exchange algorithm selected.

Client Message: CertificateVerify  =>

Exchanged data:

·      Signed handshake messages sent or received until now (optional)

If client authentication is required, the client send all the handshake messages exchanged until now signed with its private key. The server verifies the client’s identity using the public key received.

Client Message: ChangeCipherSpec  + Finished =>

Exchanged data:

·      None

End of the negotiation.

Server Message: <= ChangeCipherSpec  + Finished

Exchanged data:

·      None

End of the negotiation.

Table 1: Handshake messages exchanged during a secure communication with TLS/SSL protocol.

In the second step, the data exchange takes place. In the same way as all the other TCP/IP layers, TLS/SSL maintains the fragment-based nature of the communication. Information is divided into small data units, and each unit is encapsulated with a header that contains the information needed by the other side of the communication channel to reconstruct the original message. A MAC is appended to each data fragment, and the overall content is encrypted using the symmetric algorithm and the key materials negotiated in first step. Figure 3 illustrates the composition of a TLS/SSL data unit.

Figure 3: TLS/SSL data unit.

TLS/SSL AND THE .NET FRAMEWORK

To allow us to implement a secure communication using the TLS/SSL protocol, .NET Framework comes with the SslStream class. You can find it under the System.Net.Security namespace.

As the name suggests, SslStream is a class that derives from the Stream class. This implies that to use it, we need to apply almost the same logic as when we work with the other stream-based classes defined inside the .NET Framework. Its derivation tree is given by:

  • SslStream
  •     ∟ AuthenticatedStream
  •             ∟ Stream

... where AuthenticatedStream is an abstract class that defines common properties when working with streams that need authentication of peers before starting the communication.

There are essentially two steps involved with the authenticated stream:

  1. Perform the authentication. In this step the identity of the remote peers is checked in order to assure that data will be delivered to a trusted entity. Eventually, the remote peers can request the sender authentication too.
  2. Exchange data using the stream. After the authentication, data can be transmitted from one entity to the other by using the common methods defined for all the classes that derive from the base Stream class.

The type of authentication depends on the type of system we are considering. As we have seen, when a client and a server exchange information using the TLS/SSL protocol, authentication occurs during the handshake using the X509 digital certificate.

To perform the handshake, the SslStream class implements the AuthenticateAsServer(…) and AuthenticateAsClient(...) methods. As their names suggest, the former method occurs server side, while the latter occurs client side.

When the client invokes the AuthenticateAsClient(...) method, the handshake begins and the server uses the AuthenticateAsServer(…) method to respond to the client in order to identify itself and to negotiate the cryptographic algorithms and parameters that will be used. As developers, we can interact with the negotiation by setting the arguments required by the methods. These are summarized in Tables 2 and 3.

AuthenticateAsServer

Type

Name

Description

X509Certificate

serverCertificate

X509 certificate of the server.

Bool

clientCertificateRequired

If true, the server imposes the client authentication.

SslProtocols

enabledSslProtocols

List of TLS/SSL protocol accepted by the server.

Bool

checkCertificateRevocation

If true, the server checks whether the client’s certificate, if required, has been revoked.

Table 2: Arguments for the AuthenticateAsServer(…) method

 

AuthenticateAsClient

Type

Name

Description

String

targetHost

Server address to which the client connects.

X509CertificateCollection

clientCertificates

If client authentication is required, the list of the X509 certificates that are able to identify the client and that the server recognizes.

SslProtocols

enabledSslProtocols

Enum for the TLS/SSL protocol to use.

Bool

checkCertificateRevocation

If true, the client checks whether the server’s certificate has been revoked.

Table 3: Arguments for the AuthenticateAsClient(…) method.

If the handshake succeeds, the SslStream is ready to exchange data.

All the cryptographic parameters can be checked by querying the value of some properties implemented in the SslStream class’s object model. Table 4 shows the principal properties.

SslStream Principal Properties

Type

Name

Description

SslProtocols

SslProtocol

TLS/SSL protocol negotiated during the handshake.

ExchangeAlgortithmType

KeyExchangeAlgorithm

The key exchange algorithm adopted to exchange secret values to use during the symmetric encryption of data to be transmitted.

Int

KeyExchangeAlgorthmStrength

Length, in bytes, of the asymmetric key pairs needed by the key exchange algorithm.

CipherAlgorithmType

CipherAlgorithm

Negotiated symmetric algorithm.

Int

CipherStrength

Length, in bytes, of the symmetric key to use for the symmetric encryption of data.

HashAlgortihmType

HashAlgorithm

Hash algorithm to use for the MAC calculation.

Int

HashStrength

MAC length, in bytes.

Table 4: SslStream properties that define the values of the cryptographic parameters negotiated during the handshake.

USING THE SSLSTREAM CLASS

We are now ready to see the SslStream class in action. We will use a simple example designed for this purpose.

We start our example by defining a simple communication system able to transfer data in a client/server scenario. Our system must have the following characteristic:

  1. It must allow us to interact directly with the communication stream.
  2. The data transport must be based on the TCP protocol (Table 1) in order to assure a reliable and a connection-oriented communication. This enables us to establish a session between the client and the server. When a session has been established, the TLS 1.2 specification permits resumption of an interrupted communication maintaining the parameters negotiated during the initial handshake.

On the basis of what we have just said, the TcpListener and the TcpClient classes of the .NET Framework are the right choice for our system. We create two console applications that emulate a simple TCP server and a simple TCP client using the code in Listings 1 and 2.

SERVER SIDE

/// <summary>

/// Start the server

/// </summary>

private static void RunServer()

{

    TcpListener listener = null ;

 

        try

        {

          listener = new TcpListener (IPAddress .Any, 443);

          listener.Start();

 

          Console .WriteLine("Ready ...\n" );

 

while (String .Compare(_message, "close" ,

              StringComparison .CurrentCultureIgnoreCase ) != 0)

          {

            TcpClient client = listener.AcceptTcpClient();

            ManageClientRequest(client);

          }

 

        }

        catch (Exception ex)

        {

           Console .WriteLine("Error detected: " + ex.Message);

        }

        finally

        {

          Console .WriteLine("\nStopped ..." );

        }

}

 

Listing 1: Simple Tcp Server.

CLIENT SIDE

 

/// <summary>

       /// Start the client

/// </summary>

private static void RunClient()

{

      while (true )

        {

          Console .Write("\nInput message: " );

 

          //read a string from the console.

          _message = Console .ReadLine();

 

           if (String .Compare(_message, "close" , StringComparison .CurrentCultureIgnoreCase ) == 0) break ;

 

          if (!String .IsNullOrEmpty(_message))

          {

            //create the client instance. It will perform calls to the server's

            //endpoint (192.168.1.21:443)

            TcpClient client = new TcpClient ("192.168.1.21" , 443);

            SendMessageToServer(client);

          }

        }

Listing 2: Simple Tcp Client.

In Listings 1 and 2, the _message string variable is defined at class level.

On the server side, the RunServer() method performs the following tasks:

  1. It creates a TcpListener class by using, as communication endpoint, the IP address of the machine that hosts the server and port 443. Then it starts TcpListener .
  2. It waits for a client’s call, and for each client call, it manages the request using the ManageClientRequest (…) method.
  3. When it receives the Close message, it exits.

On the client side, the RunClient() method performs the following tasks:

  1. It reads a message from the console.
  2. It creates a TcpClient in order to send the message to the server.
  3. It send the message to the server using the SendMessageToServer() method.
  4. It repeats steps 1 and 2 until the Close message is received from the console, at which point it exits.

With the server up and running and the client ready to operate, we can now analyze how to transfer messages from the client to the server using TLS/SSL protocol. As we have seen, to accomplish this, the SendMessageToServer(…) and ManageClientRequest(…) methods have been implemented. The body of each method is shown in Listings 3 and 4.

SERVER SIDE

///

  /// <summary>

/// Manage a client request

       /// </summary>

private static void ManageClientRequest(TcpClient client)

{

       

        try

        {

          bool leaveInnerStreamOpen = true ;

 

          RemoteCertificateValidationCallback validationCallback =

            new RemoteCertificateValidationCallback (ClientValidationCallback);

 

          LocalCertificateSelectionCallback selectionCallback =

            new LocalCertificateSelectionCallback (ServerCertificateSelectionCallback);

 

          EncryptionPolicy encryptionPolicy = EncryptionPolicy .AllowNoEncryption;

 

          //create the SSL stream starting from the NetworkStream associated

   //with the TcpClient instance

          _sslStream = new SslStream (client.GetStream(),

            leaveInnerStreamOpen, validationCallback, selectionCallback, encryptionPolicy);

 

          //1. when the client requests it, the handshake begins

          ServerSideHandshake();

 

          //2. read client's data using the encrypted stream

          ReadClientData();

 

        }

        catch (Exception ex)

        {

          Console .WriteLine("\nError detected: " + ex.Message);

        }

        finally

        {

          if (_sslStream != null ) _sslStream.Close();

          client.Close();

        }

}

Listing 3: ManageClientRequest(…) method to manage a single client’s request.

CLIENT SIDE

 

/// <summary>

/// Send a message to the server using TLS/SSL

/// </summary>

private static void SendMessageToServer(TcpClient client)

{

       

        try

        {

 

          bool leaveInnerStreamOpen = false ;

 

          RemoteCertificateValidationCallback validationCallback =

            new RemoteCertificateValidationCallback (ServerValidationCallback);

 

          LocalCertificateSelectionCallback selectionCallback =

            new LocalCertificateSelectionCallback (ClientCertificateSelectionCallback);

 

          EncryptionPolicy encryptionPolicy = EncryptionPolicy .RequireEncryption;

        

          //create the SSL stream starting from the NetworkStream associated

   //with the TcpClient instance

          _sslStream = new SslStream (client.GetStream(),

            leaveInnerStreamOpen, validationCallback, selectionCallback, encryptionPolicy);

        

          //1. start the authentication process. If it doesn't succeed 

          //an AuthenticationException is thrown

          ClienSideHandshake();

 

          //2. send the input message to the server

          SendDataToServer();

 

        }

        catch (AuthenticationException ex)

        {

          Console .WriteLine("\nAuthentication Exception: " + ex.Message);

        }

        catch (Exception ex)

        {

          Console .WriteLine("\nError detected: " + ex.Message);

        }

         finally

        {

          if (_sslStream != null ) _sslStream.Close();

          client.Close();

        }

}

Listing 4: SendMessageToServer(…) method to manage a single client’s request.

In Listings 3 and 4, the _SslStream is defined at class level.

Let’s start by considering the server side method. In this, an SslStream instance is created by using, as underlying stream, the NetworkStream stream associated with the TcpClient ’s object. This object is created when theAcceptTcpClient() method of the TcpListener object informs the server that a client request has arrived.

The SslStream constructor uses four additional arguments:

  1. bool leaveInnerStreamOpen : This allows the status of the inner stream to be set (open or closed).
  2. RemoteCertificateValidationCallback validationCallback : This method is invoked on the server after the client’s certificate validation. It permits all the exception conditions that eventually occur to be managed, and allows further checks to be made prior to continuing with the handshake. The method has a return value of type Boolean. If true is returned, the handshake continues; otherwise, an AuthenticationException is thrown.
  3. LocalCertificateSelectionCallback selectionCallback : This method is invoked when the server’s certificate is selected. With it, we can perform some checks about its nature and state. The method has a return value of type X509Certificate2. We need to return to the caller the certificate selected. If null is returned, a NotSupportedException is thrown and the communication ends.
  4. EncryptionPolicy encryptionPolicy : This is an enumeration that allows us to specify whether the server requires, allows, or does not allow encryption.

The same situation occurs at client side. In this case, we create an object of type TcpClient that to communicate with the server. Its associated NetworkStream object is used to create the SslStream instance. We use, as arguments of its constructor, the same arguments with almost the same meaning as those used for the server’s code. In this case, the validationCallback method is related to the server’s certificate validation; theselectCallback method is related to the client’s certificate selection; and the encryptionPolicy value is used to specify whether the stream will be encrypted or not. The encryptionPolicy setting must be compatible with the analogue server’s setting, otherwise, an exception is thrown: if the server requires encryption, the client must require it too; if the server allows encryption, the client can use encryption or not; if the server doesn’t allow encryption, the client must not use it.

After the SslStream instance generation, the two methods continue performing the handshake. This is done by invoking the ServerSideHandshake() and ClientSideHandshake() methods. If it doesn’t succeed, an AuthenticationException is thrown and the communication ends. If it succeeds, the client sends data to the server using the SendDataToServer() method, while the server reads it using theReadClientData() method.

THE HANDSHAKE

In our example, we want to implement a TLS/SSL server with the following characteristics:

  1. It must allow both TLS 1.0 that SSL 3.0 protocols. This requirement is imposed by the SslProtocols enumeration. Table 5 lists all its possible values.
  2. It must require client authentication. The client needs a valid X509 certificate to be able to perform the data exchange.
  3. It must require that the client’s certificate is valid and hasn’t been revoked. This implies that the validationCallback delegate defined in the ManageClientRequest() method (Listing 3) will be invoked.

 

Value

Description

TLS

The TLS 1.0 protocol as defined in the RFC 2246.

SSL3

The SSL protocol, defined by Netscape Communication, from which the TLS 1.0 is derived.

SSL2

The first public available version of the SSL protocol created by Netscape (SSL 1.0 has never been published).

Default

Both TLS and SSL3 are acceptable. TLS has higher priority than SSL3.

None

Empty bit flag.

Table 5: SslProtocols enumeration values.

The client must:

  1. Require the TLS protocol.
  2. Require that the server’s certificate is valid and hasn’t been revoked. This implies that the validationCallback delegate defined in the SendMessageToServer(…) method (Listing 4) will be invoked.

We need to implement the ServerSideHandshake() and ClientSideHandshake() methods as shown in Listings 5 and 6.

SERVER SIDE

/// <summary>

/// Perform the server handshake

/// </summary>

private static void ServerSideHandshake()

{

       X509Certificate2 certificate = GetServerCertificate("ssl_server" );

 

        bool requireClientCertificate = true ;

        SslProtocols enabledSslProtocols = SslProtocols .Ssl3 | SslProtocols .Tls;

        bool checkCertificateRevocation = true ;

 

 

        _sslStream.AuthenticateAsServer

          (certificate,requireClientCertificate, enabledSslProtocols, checkCertificateRevocation);

}

Listing 5: The ServerSideHandshake(…) method.

CLIENT SIDE

/// <summary>

/// Perform the client handshake

/// </summary>

private static void ClientSideHandshake()

{

 

 

        Console .WriteLine("\nStart authentication ... " );

 

    X509CertificateCollection clientCertificates = GetClientCertificates("ssl_client" );

 

        string targetHost = "192.168.1.21" ;

        SslProtocols sslProtocol = SslProtocols .Tls;

        bool checkCertificateRevocation = true ;

 

        //Start the handshake

        _sslStream.AuthenticateAsClient

(targetHost, clientCertificates, sslProtocol, checkCertificateRevocation);

 

    

}

Listing 6: The ClientSideHandshake(…) method.

In Listings 5 and 6, the GetServerCertificate(…) and GetClientCertificates(…) methods are helper methods that allow us to load, from the certificate store, the server and client certificates respectively. Note that, while the AuthenticateAsServer(…) method requires an X509Certificate2 object as an argument, the AuthenticateAsClient(…) method requires a X509Certificate2Collection object. This is because, at this stage, the client can specify more than one certificate to authenticate itself. The certificate used will be selected a second time, and it must be a certificate that the server is able to recognize. We will see how this is done in section 4.3.

VALIDATION

During the handshake, both client and server are able to validate the information they exchange. As seen in Listings 3 and 4, the validation results are carried out with the aid of the ServerValidationCallback(…) and ClientValidationCallback(… ) methods. They are implemented as shown in Listings 7 and 8.

SERVER SIDE

///   <summary>

///  Callback for the verification of the client's certificate

///   </summary>

private   static   bool  ClientValidationCallback

  ( object  sender,  X509Certificate  certificate,  X509Chain  chain,  SslPolicyErrors  sslPolicyErrors)

{

   switch  (sslPolicyErrors)

  {

 

     case   SslPolicyErrors .RemoteCertificateNameMismatch:

       Console .WriteLine( "Client's name mismatch. End communication ...\n" );

       return   false ;

 

     case   SslPolicyErrors .RemoteCertificateNotAvailable:

       Console .WriteLine( "Client's certificate not available. End communication ...\n" );

       return   false ;

 

     case   SslPolicyErrors .RemoteCertificateChainErrors:

       Console .WriteLine( "Client's certificate validation failed. End communication ...\n" );

       return   false ;

 

  }

 

   //Perform others checks using the "certificate" and "chain" objects ...

 

   // ...

   // ...

 

   Console .WriteLine( "Client's authentication succeeded ...\n" );

 

   return   true ;

}

Listing 7: ClientValidationCallback(…) method.

CLIENT SIDE

///   <summary>     
///  Callback for the verification of the server certificate     
///
  </summary>     
private
  static   bool  ServerValidationCallback

       ( object  sender,  X509Certificate  certificate,  X509Chain  chain,  SslPolicyErrors  sslPolicyErrors)

     {       

switch  (sslPolicyErrors)

      {

          case   SslPolicyErrors .RemoteCertificateNameMismatch:

           Console .WriteLine( "Server name mismatch. End communication ...\n" );

            return   false ;

          case   SslPolicyErrors .RemoteCertificateNotAvailable:

            Console .WriteLine( "Server's certificate not available. End communication ...\n" );

            return   false ;

          case   SslPolicyErrors .RemoteCertificateChainErrors:

            Console .WriteLine( "Server's certificate validation failed. End communication ...\n" );

            return   false ;

       }        //Perform others checks using the "certificate" and "chain" objects ...

        // ...

        // ...

        Console .WriteLine( "Server's authentication succeeded ...\n" );

       return   true ;

     }

Listing 8: ServerValidationCallback(…) method.

The two methods in Listings 7 and 8 are quite similar. They get, as method arguments, the certificate being validated, the certificate chain (given by the collection of all the certification authorities’ and sub-certification authorities’ certificates involved when it was issued, plus information about how they relate to each other) and an SslPolicyErrors enumeration value that states which kind of exception eventually occurs during the validation. For this last, three values are possible:

  • RemoteCertificateNameMismatch : occurs when the server certificate’s common name is not identical to the server name.
  • RemoteCertificateNotAvailable : occurs when a valid certificate to use for the communication has not been found.
  • RemoteCertificateChainErrors : occurs when the certificate cannot be validated by using its certificate chain; or when the certificate has been revoked; or when the certificate revocation list cannot be found and the server or the client needs to check the certificate’s revocation status (because the checkCertificateRevocation argument in Listings 5 and 6 is true).

When one of these exceptions occurs, or when we are not satisfied by further checks that we can perform by using the certificate or the certificate chain, we can end the communication by forcing the method to return the “false” value. If all goes well or we want to proceed anyway, we only need to return the “true” value.

CERTIFICATES SELECTION

SslServer’s class allows us to interact with the certificate selection both client side and server side. We have seen that when we create the SslStream ’s class instances, we can optionally specify a callback to use for this purpose. In our example, we have implemented the ServerCertificateSelectionCallback(…) and ClientCertificateSelectionCallback(…) methods. The method body for each is shown in Listings 9 and 10.

Server Side

///   <summary>     
///  Certificate selection callback.     
///   </summary>     
public
  static   X509Certificate  ServerCertificateSelectionCallback ( object  sender,  string  targetHost, X509CertificateCollection  localCertificates,   X509Certificate  remoteCertificate,  string [] acceptableIssuers)
     {
        //perform some checks on the certificate...
         // ...
        // ...                                       //return the selected certificate. If null is returned a 
       // NotSupported exception is thrown.
        return  localCertificates[0];
    }

Listing 9: ServerCertificateSelectionCallback(…) method.

CLIENT SIDE

///   <summary>     
///  Certificate selection callback.     
///   </summary>    
 
public   static   X509Certificate  ClientCertificateSelectionCallback ( object  sender, string  targetHost, X509CertificateCollection  localCertificates,   X509Certificate  remoteCertificate,  string [] acceptableIssuers)
     {
      
//perform some checks on the certificate ...
        // ...
       // ...
        //return the selected certificate. If null is returned, the client’s authentication does
 
      //not take place.
      
return  localCertificates[0];     }

Listing 10: ClientCertificateSelectionCallback(…) method.

They are almost identical. They receive the following parameters as input:

  • sender : A reference to the SslStream object used for the communication.
  • targetHost : The address of the remote host.
  • localCertificates : The collection of certificates that was used as the argument for the SslStream constructor when it was instantiated, plus all the certificates that belong to their chain.
  • remoteCertificate : The certificate of the remote entity involved in the secure communication.
  • acceptableIssuers : An array of all the certificate issuer’s distinguished names that are allowed.

The two methods must return an X509Certificate2 object, which is the certificate that will be used during the handshake.

My tests have brought me to the following conclusions:

  1. The server side method is not useful at all. In each situation, only the sender and the localCertificates argument has a value different from null (or empty array). When the method returns null, a NotSupportedException exception is thrown. Maybe the callback has been implemented only for the client side. Perhaps, rather than implementing two different SslStream classes, (one for the server, and a different one for the client, with the LocalCertificateSelectionCallback(...) implementation), the .NET Framework teams decided to implement a single class for both, giving a dummy meaning to the server side’s LocalCertificateSelectionCallback(...) delegate.
  2. During the handshake, the client side method is invoked twice. In the first invocation, the situation is very similar to the above. If we return a bad certificate (maybe a revoked one), the handshake proceeds without errors. In the second call, the acceptableIssuers array has values too, as does the localCertificates collection. The former contains the distinguished name of all the issuers that the server is able to recognize; the latter is filled with all the certificates that we used as argument for the AuthenticateAsClient(…) method. If we return a bad certificate, a RemoteCertificateChainErrors is thrown. If we return null, the handshake will proceed anyway but without the client validation (we can check this condition by checking the value of theIsMutuallyAuthenticated property of the SslStream class). If we take a look at Table 1, it is reasonable to assume that the two calls are executed during the Certificate message and the CertificateVerify message. If the first doesn’t need a certificate validation, in the second case, it is required.

DATA EXCHANGE

If the handshake ends properly, data exchange can take place. For this, we only need to use the common methods related to the Stream base class. We can use the code shown in Listings 11 and 12:

SERVER SIDE

///   <summary>

///  Read client's data

///   </summary>

private   static   void  ReadClientData()

{

   byte [] buffer =  new   byte [1024];

 

   int  n = _sslStream.Read(buffer, 0, 1024);

   Array .Resize< byte >( ref  buffer, n);

 

  _message =  Encoding .UTF8.GetString(buffer);

 

   Console .WriteLine( "Client said: "  + _message);

}

Listing 11: ReadClientData (…) method.

CLIENT SIDE

///   <summary>

///  Send data to server

///   </summary>

private   static   void  SendDataToServer()

{

   byte [] buffer =  Encoding .UTF8.GetBytes(_message);

  _sslStream.Write(buffer, 0, buffer.Length);

 

}

Listing 12: SendDataToServer(…) method.

The client uses the Write(…) method of the SslStream class instance to write each message acquired by the console into the SSL stream. The server reads it by using the Read(…) method of its SslStream class instance.

CIPHERSUITES

We are now able to use our example to investigate how the SslStream class behaves in relation to the different settings that we are allowed to set. The SslStream class defined inside the .NET Framework allows us to:

§ Vary the TLS/SSL protocol to use.

§ Vary the characteristics of the certificates to use in term of keys lengths, asymmetric algorithms, and hash algorithms.

All the others parameters (key exchange algorithm, symmetric algorithm to use for the encryption of the stream’s data, keys strength etc…) are determined accordingly.

We would like to search for the CipherSuite that the SslStream class negotiates. CipherSuites are defined in each TLS or SSL protocol version, and they are stated with some combination of (SignatureAlgorithm)–KeyExhangeAlgortihm–SymmetricAlgorithm-HashAlgorithm. The SslStream class doesn’t allow us to select a CipherSuite , so we would like to check which is used.

We perform our analysis by obtaining a set of the so called SSL certificates for the server and SSL certificates for the client. Table 6 shows the principal characteristics they need to have in order to be acceptable for our purpose.

Characteristic

Ssl Server

Ssl Client

Common Name

Server’s DNS name or IP

Any

KeyUsage

Digital Signature, Key Encipherment

Digital Signature 

EnhancedKeyUsage

Server Authentication (1.3.6.1.5.5.7.3.1)

Client Authentication (1.3.6.1.5.5.7.3.2)

Table 6: SSL Server certificate and SSL Client certificate’s main properties.

We need to have the certificate revocation list associated with them too, and install it on our machine. If we don’t have it, we must set to false the checkCertificateRevocation argument of the AuthenticateAsServer(…) and AuthenticateAsClient(…) methods in order to avoid exceptions.

On the basis of some tests that I made, I have found that the client authentication occurs even if we use a digital signature’s certificate for the client. In my opinion, this is not a good thing. Whatever the certification authority we use, private or commercial, the issuing of a certificate implies (or should imply) the acceptance of some certificate policies that state under which condition the certificate is valid (for more information see the RFC3647 )). Suppose that a certification authority issues a digital signature’s certificate to an entity that must use it only to sign documents. What if the entity uses the same certificate to access reserved, for it, data stored in a remote server that uses TLS/SSL and client authentication? To avoid this you can use the ServerValidationCallback(…) delegate defined in Listing 8 to check the EnhancedKeyUsage extension value of the client’s certificate and refuse the communication if its Object Identifier (OID) is not 1.3.6.1.5.5.7.3.2.

After the handshake, the SSL stream’s properties can be investigated by checking the value of the SslStream class properties reported in Table 4. Table 7 shows the result obtained for each allowed protocol when certificates are generate using the RSA algorithm.

Protocol

KEA

SYM (bit)

HSH (bit)

CipherSuite

TLS1.0

RSAKeyX

AES (128)

SHA1 (160)

TLS_RSA_WITH_AES_128_CBC_SHA    

SSL3.0

RSAKeyX

RC4 (128)

SHA1 (160)

SSL_RSA_WITH_RC4_128_SHA

SSL2.0

RSAKeyX

RC4 (128)

MD5 (128)

SSL_CK_RC4_128_WITH_MD5

Table 7: Ciphersuites used by the sslstream class for each supported tls/ssl protocol. kea: key exchange algorithm (the key strength depends on the certificate keys size), sym: symmetric algorithm (brackets the key length in bit), hsh: hash algorithm for the mac computation (brackets the hash size in bit)

CONCLUSION

As you have seen, to implement at low level a secure communication channel by using the TLS/SSL protocol isn’t a trivial task. It requires at least a basic knowledge about symmetric and asymmetric cryptography, hash calculation and public key infrastructure (PKI). Even if in the last few years a large demand for security has taken place, the complexity behind it sometimes is the reason why it is put aside. I hope this article can help you to better understand the TLS/SSL protocol and the principles behind it thus simplifying your job when dealing with it.

Matteo Slaviero

Author profile:

Matteo Slaviero works as Microsoft .NET Framework consultant. He recently started his own company, Cassandra, to develop new products and services related to the world of cryptography (symmetric encryption, digital signature, X509 digital certificates generation and more). . Follow him on Twitter

Search for other articles by Matteo Slaviero

Rate this article:   Avg rating: from a total of 39 votes.


Poor

OK

Good

Great

Must read
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: TLS 1.1 or TLS 1.2 support in .net?
Posted by: omight (view profile)
Posted on: Monday, January 09, 2012 at 3:17 AM
Message: Hi Matteo,

Thanks for a very informative article.

So what is the status of TLS 1.1 TLS 1.2 support in .net? I'm a bit confused that table 5 and table 7 only list TLS 1.0.
I would have expected support for TLS 1.1 or TLS 1.2? Or is TLS 1.1/TLS 1.2 configurable?

Subject: How to create certificate?
Posted by: yudha (view profile)
Posted on: Wednesday, January 18, 2012 at 1:33 PM
Message: Good article, thanks for sharing.

I tried running the program you created, I found the error as below:

Client's certificate validation failed. End communication ...
Error detected: The remote certificate is invalid according to the validation procedure.

How do I create valid certificates? Thanks

Subject: Sample code link expired :-(
Posted by: grais (view profile)
Posted on: Sunday, November 18, 2012 at 5:16 AM
Message: The http://bit.ly/ebN3P4 not work. Please upgrade to.
Thanks.

 

Top Rated

Acceptance Testing with FitNesse: Multiplicities and Comparisons
 FitNesse is one of the most popular tools for unit testing since it is designed with a Wiki-style... Read more...

Acceptance Testing with FitNesse: Symbols, Variables and Code-behind Styles
 Although FitNesse can be used as a generic automated testing tool for both applications and databases,... Read more...

Acceptance Testing with FitNesse: Documentation and Infrastructure
 FitNesse is a popular general-purpose wiki-based framework for writing acceptance tests for software... Read more...

TortoiseSVN and Subversion Cookbook Part 11: Subversion and Oracle
 It is only recently that the tools have existed to make source-control easy for database developers.... Read more...

TortoiseSVN and Subversion Cookbook Part 10: Extending the reach of Subversion
 Subversion provides a good way of source-controlling a database, but many operations are best done from... Read more...

Most Viewed

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Calling Cross Domain Web Services in AJAX
 The latest craze for mashups involves making cross-domain calls to Web Services from APIs made publicly... Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Why Join

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.