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

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

What you need to know

Technologies

Prerequisites

  • The following examples in this topic are provided in JavaScript. 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.



    // Define some global variables that can be used from
    // multiple functions as needed 
    var clientSocket = null;
    var serverHostName = null;
    var serverServiceName = null;


    function openClient() {
        clientSocket = new Windows.Networking.Sockets.StreamSocket();
        // Try to connect to contoso using HTTPS (port 443)
        serverHostName = new Windows.Networking.HostName("www.contoso.com");
        serverServiceName = "https";

        // Call connectAsync method with SSL
        clientSocket.connectAsync(serverHostName, serverServiceName, Sockets.SocketProtectionLevel.Ssl).done(onClientAccept, onConnectError);
    }

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

    // If the client connection was accepted, display
    // a message to the user
    function onClientAccept() {
        socketSample.displayStatus("Client: connection completed.");
    }

    // The connection failed so display an error message
    // Could retry the connection, but for this simple example
    // just close the socket.
    function onConnectError(reason) {
       socketsSample.displayStatus(reason);
       clientSocket.close();
       clientSocket = null; 
    }

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.



    // Define some global variables that can be used from
    // multiple functions as needed
    var clientSocket = null;
    var serverHostName = null;
    var serverServiceName = null;

    function openClient() {
        clientSocket = new Windows.Networking.Sockets.StreamSocket();
        // Try to connect to contoso initially using HTTP
        serverHostName = new Windows.Networking.HostName("www.contoso.com");
        serverServiceName = "http";

        // Call ConnectAsync method to establish initial connection
        clientSocket.connectAsync(serverHostName, serverServiceName).done(onClientAccept, onConnectError);
    }

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

    // If the client connection was accepted, display
    // a message to the user
    function onClientAccept() {
        socketSample.displayStatus("Client: connection completed.");
        sendHello();
    }

    // The connection failed so display an error message
    // We could retry the connection, but for this simple example
    // we just close the socket.
    function onConnectError(reason) {
        socketsSample.displayStatus(reason);
        clientSocket.close();
        clientSocket = null; 
    }

    // Send some data in a simple format that is
    // the length of the string of data in a 4-byte integer
    // followed by the string
    function sendHello() {
        if (!clientSocket) {
            socketsSample.displayStatus("Client: you must connect the client before using it.");
            return;
        }
        var writer = new Windows.Storage.Streams.DataWriter(socketsSample.clientSocket.outputStream);
        var string = "Hello World ☺ ";
        var len = writer.measureString(string); // Gets the UTF-8 string length.
        writer.writeInt32(len);
        writer.writeString(string);
        socketsSample.displayStatus("Client: sending hello.");
        writer.storeAsync().done(onStore, onSendError);
        writer.detachStream(); // Detach stream, if not, DataWriter destructor will close it.
    }

    function onStore() {
        socketsSample.displayStatus("Client: sent hello.");
        upgradeClient();
    }

    function onSendError(reason) {
        socketsSample.displayStatus(reason);
        clientSocket.close();
        clientSocket = null; 
    }

    function upgradeClient() {
        if (!clientSocket) {
            socketsSample.displayStatus("Client: you must connect the client before using it.");
            return;
        }
        var validationName = serverHostName;
        upgradeToSslAsync(SocketProtectionLevel.Ssl, serverHostName).done(onUpgradeAccept, onUpgradeError);
    }         

    // If upgrade to SSL was successful, display message to user
    function onUpgradeAccept() {
        socketSample.displayStatus("Client: upgrade to SSL completed.");
    }

    // The upgrade connection failed so display an error message
    // We could retry the upgrade possibly changing the validationHostname, 
    // but for this simple example we just close the socket.
    function onUpgradeError(reason) {
        socketsSample.displayStatus(reason);
        clientSocket.close();
        clientSocket = null; 
    }

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 datagram socket

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

Samples

StreamSocket sample