How to secure socket connections with TLS/SSL (XAML)

This topic shows how to secure stream socket connections with TLS/SSL when using a StreamSocket in a Windows Runtime app.

What you need to know

Technologies

Prerequisites

  • The following examples in this topic are provided in C# and C++. A basic understanding of sockets and the use of SSL/TLS is recommended.

Overview of a SSL/TLS connection

Secure Sockets Layer (SSL) and the more recent Transport Layer Security (TLS) are cryptographic protocols designed to provide authentication and encryption for network communication. These protocols are designed to prevent eavesdropping and tampering when sending and receiving network data. These protocols use a client-server model for the protocol exchanges. These protocols also use digital certificates and certificate authorities to verify that the server is who it claims to be. The TLS protocol is documented in IETF RFC 5246. The earlier SSL protocol was documented by Netscape Communications. SSL is commonly used to refer to both of these protocols.

The StreamSocket object can be configured to use SSL/TLS for communications between the client and the server. This support for SSL/TLS is limited to using the StreamSocket object as the client in the SSL/TLS negotiation. SSL/TLS cannot currently be used by the StreamSocketListener with the StreamSocket created when a connection is received to enable SSL/TLS on the StreamSocket created, since the SSL/TLS negotiation as a server is not implemented for a StreamSocket. The client support for SSL/TLS does not include the ability to use client certificates.

There are two ways to secure a StreamSocket connection with SSL/TLS:

  • ConnectAsync - Make the initial connection to a network service and negotiate immediately to use SSL/TLS for all communications.
  • UpgradeToSslAsync - Connect initially to a network service without encryption. The app may send or receive data. Then, upgrade the connection to use SSL/TLS for all further communications.

Use ConnectAsync

Establishes the initial connection with a network service and then negotiates immediately to use SSL/TLS for all communications. There are two ConnectAsync methods that support passing a protectionLevel parameter:

If the protectionLevel parameter is set to Windows.Networking.Sockets.SocketProtectionLevel.Ssl when calling either of the above ConnectAsync methods, the StreamSocket that must use SSL/TLS for encryption. This value requires encryption and never allows a NULL cipher to be used.

The normal sequence to use with one of these ConnectAsync methods is the same.

  • Create a StreamSocket.
  • If an advanced option on the socket is needed, use the StreamSocket.Control property to get the StreamSocketControl instance associated with a StreamSocket object. Set a property on the StreamSocketControl.
  • Call one of the above ConnectAsync methods to start an operation to connect to a remote destination and immediately negotiate the use of SSL/TLS.

The following example creates a StreamSocket and tries to establish a connection to the network service and negotiate immediately to use SSL/TLS. If the negotiation is successful, all network communication using the StreamSocket between the client the network server will be encrypted.

using Windows.Networking;
using Windows.Networking.Sockets;


    // Define some variables and set values
    StreamSocket clientSocket = new StreamSocket();
 
    HostName serverHost = new HostName("www.contoso.com");
    string serverServiceName = "https";

    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages 

    // Try to connect to contoso using HTTPS (port 443)
    try {

        // Call ConnectAsync method with SSL
        await clientSocket.ConnectAsync(serverHost, serverServiceName, SocketProtectionLevel.Ssl);

        NotifyUser("Connected");
    }
    catch (Exception exception) {
        // If this is an unknown status it means that the error is fatal and retry will likely fail.
        if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) {
            throw;
        }

        NotifyUser("Connect failed with error: " + exception.Message);
        // Could retry the connection, but for this simple example
        // just close the socket.

        clientSocket.Dispose();
        clientSocket = null; 
    }
       
    // Add code to send and receive data using the clientSocket
    // and then close the clientSocket
using Windows::Networking;
using Windows::Networking::Sockets;


    // Define some variables and set values
    StreamSocket^ clientSocket = new ref StreamSocket();
 
    HostName^ serverHost = new ref HostName("www.contoso.com");
    String serverServiceName = "https";

    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages 

    // Try to connect to the server using HTTPS and SSL (port 443)
    task<void>(clientSocket->ConnectAsync(serverHost, serverServiceName, SocketProtectionLevel::SSL)).then([this] (task<void> previousTask) {
        try
        {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.Get();
            NotifyUser("Connected");

        }
        catch (Exception^ exception)
        {
            NotifyUser("Connect failed with error: " + exception->Message);
 
            clientSocket.Close();
            clientSocket = null;
        }
    });
       
    // Add code to send and receive data using the clientSocket
    // Then close the clientSocket when done

Use UpgradeToSslAsync

Establishes an initial connection to a network service without encryption. The app may send or receive some data, then upgrade the connection to use SSL/TLS for all further communications. This uses the following method:

The UpgradeToSslAsync method takes two parameters. The protectionLevel parameter indicates the protection level desired. The validationHostName parameter is the hostname of the remote network destination that is used for validation when upgrading to SSL. Normally the validationHostName would be the same hostname that the app used to initially establish the connection. If the protectionLevel parameter is set to Windows.System.Socket.SocketProtectionLevel.Ssl when calling the above UpgradeToSslAsync method, the StreamSocket must use the SSL/TLS for encryption. This value requires encryption and never allows a NULL cipher to be used.

The normal sequence to use with the UpgradeToSslAsync method is as follows:

The following example creates a StreamSocket, tries to establish a connection to the network service, sends some initial data, and then negotiates to use SSL/TLS. If the negotiation is successful, all network communication using the StreamSocket between the client and the network server will be encrypted.

using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

    // Define some variables and set values
    StreamSocket clientSocket = new StreamSocket();
 
    HostName serverHost = new HostName("www.contoso.com");
    string serverServiceName = "http";

    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages 

    // Try to connect to contoso using HTTP (port 80)
    try {
        // Call ConnectAsync method with a plain socket
        await clientSocket.ConnectAsync(serverHost, serverServiceName, SocketProtectionLevel.PlainSocket);

        NotifyUser("Connected");

    }
    catch (Exception exception) {
        // If this is an unknown status it means that the error is fatal and retry will likely fail.
        if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) {
            throw;
        }

        NotifyUser("Connect failed with error: " + exception.Message, NotifyType.ErrorMessage);
        // Could retry the connection, but for this simple example
        // just close the socket.

        clientSocket.Dispose();
        clientSocket = null; 
        return;
    }

    // Now try to sent some data
    DataWriter writer = new DataWriter(clientSocket.OutputStream);
    string hello = "Hello World ☺ ";
    Int32 len = (int) writer.MeasureString(hello); // Gets the UTF-8 string length.
    writer.WriteInt32(len);
    writer.WriteString(hello);
    NotifyUser("Client: sending hello");

    try {
        // Call StoreAsync method to store the hello message
        await writer.StoreAsync();

        NotifyUser("Client: sent data");

        writer.DetachStream(); // Detach stream, if not, DataWriter destructor will close it.
    }
    catch (Exception exception) {
        NotifyUser("Store failed with error: " + exception.Message);
        // Could retry the store, but for this simple example
            // just close the socket.

            clientSocket.Dispose();
            clientSocket = null; 
            return;
    }

    // Now upgrade the client to use SSL
    try {
        // Try to upgrade to SSL
        await clientSocket.UpgradeToSslAsync(SocketProtectionLevel.Ssl, serverHost);

        NotifyUser("Client: upgrade to SSL completed");
           
        // Add code to send and receive data 
        // The close clientSocket when done
    }
    catch (Exception exception) {
        // If this is an unknown status it means that the error is fatal and retry will likely fail.
        if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) {
            throw;
        }

        NotifyUser("Upgrade to SSL failed with error: " + exception.Message);

        clientSocket.Dispose();
        clientSocket = null; 
        return;
    }
    
using Windows::Networking;
using Windows::Networking::Sockets;
using Windows::Storage::Streams;


    // Define some variables and set values
    StreamSocket^ clientSocket = new ref StreamSocket();
 
    Hostname^ serverHost = new ref HostName("www.contoso.com");
    String serverServiceName = "http";

    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages 

    // Try to connect to contoso using HTTP (port 80)
    task<void>(clientSocket->ConnectAsync(serverHost, serverServiceName, SocketProtectionLevel::PlainSocket)).then([this] (task<void> previousTask) {
        try
        {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.Get();
            NotifyUser("Connected");

        }
        catch (Exception^ exception)
        {
            NotifyUser("Connect failed with error: " + exception->Message);
 
            clientSocket->Close();
            clientSocket = null;
        }
    });
       
    // Now try to sent some data
    DataWriter^ writer = new ref DataWriter(clientSocket.OutputStream);
    String hello = "Hello World ☺ ";
    Int32 len = (int) writer->MeasureString(hello); // Gets the UTF-8 string length.
    writer->writeInt32(len);
    writer->writeString(hello);
    NotifyUser("Client: sending hello");

    task<void>(writer->StoreAsync()).then([this] (task<void> previousTask) {
        try {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.Get();

            NotifyUser("Client: sent hello");

            writer->DetachStream(); // Detach stream, if not, DataWriter destructor will close it.
       }
       catch (Exception^ exception) {
               NotifyUser("Store failed with error: " + exception->Message);
               // Could retry the store, but for this simple example
               // just close the socket.
 
               clientSocket->Close();
               clientSocket = null;
               return
       }
    });

    // Now upgrade the client to use SSL
    task<void>(clientSocket->UpgradeToSslAsync(clientSocket.SocketProtectionLevel.Ssl, serverHost)).then([this] (task<void> previousTask) {
        try {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.Get();

           NotifyUser(Client: upgrade to SSL completed");
           
           // Add code to send and receive data 
           // Then close clientSocket when done
        }
        catch (Exception^ exception) {
            // If this is an unknown status it means that the error is fatal and retry will likely fail.
            if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) {
                throw;
            }

            NotifyUser("Upgrade to SSL failed with error: " + exception.Message);

            clientSocket->Close();
            clientSocket = null; 
            return;
        }
    });

Remarks

The SocketProtectionLevel enumeration has several possible values:

  • PlainSocket - A plain socket with no encryption.

  • Ssl - A socket that must use the SSL/TLS for encryption. This value requires encryption and never allows a NULL cipher.

    This value supports the SSL 3.0 and TLS 1.0 protocols and all encryption ciphers installed on the system except the NULL cipher.

  • SslAllowNullEncryption - A socket that prefers to use the SSL/TLS for encryption. This value prefers that full encryption be used, but allows a NULL cipher (no encryption) based on the server configuration.

  • BluetoothEncryptionAllowNullAuthentication - A Bluetooth socket that prefers that encryption be used, but allows a NULL cipher (no encryption) based on the configuration of the target server.

  • BluetoothEncryptionWithAuthentication - A Bluetooth socket that must use encryption. This value requires encryption and never allows a NULL cipher.

  • Ssl3AllowWeakEncryption - A TCP socket that must use SSL for encryption. This value supports the SSL 3.0 protocol and all encryption ciphers installed on the system except the NULL cipher. This value allows RC4 and other weak ciphers which are considered insecure.

  • Tls10 - A TCP socket that must use SSL for encryption. This value supports the TLS 1.0 protocol and all encryption ciphers installed on the system except RC4, other weak ciphers, and the NULL cipher.

  • Tls11 - A TCP socket that must use SSL for encryption. This value supports the TLS 1.1 and TLS 1.0 protocols and all encryption ciphers installed on the system except RC4, other weak ciphers, and the NULL cipher.

  • Tls12 - A TCP socket that must use SSL for encryption. This value supports the TLS 1.2, TLS 1.1 and TLS 1.0 protocols and all encryption ciphers installed on the system except RC4, other weak ciphers, and the NULL cipher.

The SslAllowNullEncryption value is not normally used since it would allow a NULL cipher to be used, which represents no encryption, so network communication might not be encrypted. The SslAllowNullEncryption value does allow the SSL/TLS negotiation to validate the server based on the server digital certificate and the certificate authority.

The SSL strength actually negotiated using ConnectAsync or UpgradeToSslAsync can be determined by getting the StreamSocketinformation.ProtectionLevel property.

Other

Connecting with sockets

How to connect with a stream socket

How to use advanced socket controls

Reference

SocketProtectionLevel

StreamSocket

StreamSocket.ConnectAsync

StreamSocket.UpgradeToSslAsync

StreamSocketinformation.ProtectionLevel

Windows.Networking.Sockets