Wire-Level Security

Consider a pay-for web service such as Amazon’s S3 storage service. This service needs to authenticate requests to store and retrieve data so that only the paying clients have access to the service and that, moreover, a particular client has privileged access to its paid-for storage. In the RESTful version of S3, Amazon uses a customization of keyed HMAC to authenticate client requests. Amazon allows the authentication credential to be in either the query string (which is part of the HTTP headers) or in another header key/value pair with Authorization as the key. In either case, the basic approach is the same:

A message digest

Figure 6-1. A message digest

What prevents a client’s request to Amazon S3 from being intercepted and the value of its Authorization header, the Amazon authentication signature, from being pirated? Amazon assumes that the request is sent over the secure communications channel that HTTPS (HyperText Transport Protocol Secure) provides. HTTPS is HTTP with an added security layer. Netscape did the original work in the design and implementation of this security layer and called it SSL. The IETF (International Engineering Task Force) has taken over SSL and renamed it TLS (Transport Layer Security). Although SSL and TLS differ in version numbers and in some technical details, they will be treated as basically the same here. In any case, it is common to use SSL, TLS, and SSL/TLS interchangeably.

Java has various packages that support SSL/TLS in general and HTTPS in particular. The JSSE (Java Secure Sockets Extension) API, which covers the packages javax.net and javax.net.ssl, has been part of core Java since JDK 1.4. Of interest here is that higher levels of security, such as user authentication, usually require wire-level security of the kind that HTTPS provides. Accordingly, the discussion of web services security begins with HTTPS and the wire-level security that this protocol provides.

HTTPS is easily the most popular among the secure versions of HTTP. HTTPS provides three critical security services over and above the transport services that HTTP provides. The following is a summary of the three (see Figure 6-2). In the figure, Alice needs to send a secret message to Bob. Eve, however, may be eavesdropping. Eve may try to dupe Alice and Bob into believing that they are communicating with one another when, in fact, each is communicating instead with Eve. This scenario is known as the MITM (Man in the Middle) attack. For secure communications, Alice and Bob need these three services:

These features can be implemented in different ways. Before considering how HTTPS implements the three features, it will be useful to look briefly at data encryption and decryption because confidentiality is among the three services that HTTPS provides.

Modern approaches to encryption follow two different approaches: symmetric and asymmetric. Under either approach, the bits to be encrypted (plain bits) are one input to an encryption engine. An encryption key is the other input (see Figure 6-3).

The encrypted bits are the cipher bits. If the input bits represent text, then they are the plaintext and the output bits are the ciphertext. The cipher bits are one input to the decryption engine; a decryption key is the other input. The decryption produces the original plain bits. In the symmetric approach, the same key—called the secret or single key—is used to encrypt and decrypt (see Figure 6-4). The symmetric approach has the advantage of being relatively fast but the disadvantage of what is known as the key distribution problem. How is the secret key itself to be distributed to the sender and the receiver?

In the asymmetric approach, the starting point is a key pair, which consists of a private key and a public key. As the names suggest, the private key should not be distributed but, rather, safeguarded by whoever generated the key pair. The public key can be distributed freely and publicly. If message bits are encrypted with the public key, they can be decrypted only with the private key—and vice versa. Figure 6-5 illustrates. The asymmetric approach solves the key distribution problem, but asymmetric encryption and decryption are roughly a thousand times slower than their symmetric counterparts.

The public key approach solves the confidentiality problem for Alice and Bob. If Alice encrypts the message with the public key from Bob’s key pair, and Bob has the only copy of the private key from this pair, then only Bob can decrypt the message. Even if Eve intercepts Alice’s message, she cannot decrypt the message with Bob’s public key.

Of the three required security services—peer authentication, confidentiality, and integrity—the last is the least complicated. The message sent over HTTPS includes a digest value, which the receiver recomputes. If the sent digest value differs from the digest value that the receiver computes, then the message was altered during transmission, either by accident or design. If the sent digest value itself is altered during transmission, this likewise counts as integrity failure.

HTTPS handles peer authentication through the exchange of digital certificates. In many cases, however, it is only the client that challenges the server. Consider a typical website application in which a shopper finalizes an order for the contents of his shopping cart by submitting a credit card number to the vendor. Here is a summary of what typically happens when the client-side browser and the web server negotiate to set up an HTTPS connection:

The usually one-sided authentication challenge at play in websites, with the client challenging the server but not the other way around, shows up in Tomcat’s configuration file, TOMCAT_HOME/conf/server.xml. Here is the entry for HTTPS:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
           maxThreads="150" scheme="https" secure="true"
           clientAuth="false"   1
           sslProtocol="TLS"/>

The clientAuth attribute is set to false (line 1), thereby indicating that Tomcat does not challenge the client. If the clientAuth attribute were set to true, then Tomcat would challenge the client’s user agent; a setting of true might be of interest for web services in particular. In this configuration file, there is no setting for a serverAuth because the default client behavior is to challenge the server.

For authentication and confidentiality, HTTPS relies on digital certificates, which are widely used in public key cryptography precisely because the exchange of secret keys is so difficult among many users. Here is a summary of how HTTPS authentication is intertwined with HTTPS confidentiality. The process is sometimes described as the handshake between client and server that culminates in a secure network connection. In this scenario, the client might be a browser or an application functioning as a web service client. For convenience, the term web server covers both a standard website server such as Tomcat or a full-bodied Java Application Server such as Oracle WebLogic, GlassFish, JBoss, or WebSphere:

A secret key is used to encrypt and decrypt traffic for several reasons. First, symmetric encryption has relatively high performance. Second, if the server does not challenge the client, then the server does not have the client’s public key to encrypt messages to the client. The server cannot encrypt messages with its own private key, as any receiver (for instance, Eve) with access to the server’s public key then could decrypt the message. Finally, encrypting and decrypting with two separate key pairs is inherently trickier—and significantly slower—than using a shared secret key.

The primary challenge is to get the pre-master secret securely from the client to the server, and the server’s public key, available to the client in the server’s digital certificate after the mutual challenge phase, fits the bill perfectly. The master secret key is generated only after the client and the server have agreed upon which cipher suite, or set of cryptographic algorithms, should be used. A cipher suite, including a key-pair algorithm and a hash algorithm, will be examined through a code example shortly.

Although digital certificates now play a dominant role in mutual challenge scenarios, they are not the only game in town. For example, SRP (Secure Remote Protocol) implements mutual challenge but without digital certificates. For more on SRP, see srp.stanford.edu.

It is time to flesh out these architectural sketches with a code example. The class HttpsURLConnection, which extends the HttpURLConnection class, supports HTTPS connections. The GoogleClient application (see Example 6-1) uses this class to issue a GET request under HTTPS against Google’s home site. Note that the port number in the URL is 443 (line 1), the standard port for HTTPS connections.

The endpoint URL for an HTTPS connection to Google begins with https (known in HTTP as the scheme), and the port number, 443, is the standard one for HTTPS connections. Modern web servers typically accept HTTP connections on port 80 and HTTPS connections on port 443, although these numbers are configurable. For example, Tomcat by default listens on port 8080 for HTTP connections and on port 8443 for HTTPS connections. The HTTPS URL for Google is line 1 in the code listing.

The GoogleClient next opens an HTTPS connection (line 2) and prepares for a GET request against Google (line 3). Once the connection is made, the application invokes dumpDetails to print information about the secure connection: the type of certificates returned from Google during the challenge, the hash code or fingerprint that identifies each certificate, the algorithm used to generate the key pair, and the format of the certificate (lines 5 through 9 in the code listing). The dumpDetails method also prints the response code, the by now familiar 200 that signals a successful GET request. On a sample run, the output from dumpDetails was:

Status code:  200
Cipher suite: TLS_ECDHE_RSA_WITH_RC4_128_SHA 1
        Cert. type: X.509
        Hash code:  12584213
        Algorithm:  RSA
        Format:     X.509

        Cert. type: X.509
        Hash code:  2815543
        Algorithm:  RSA
        Format:     X.509

Line 1 in the listing gives the cipher suite, a collection of information about the algorithms used in the initial handshake and in the encryption/decryption of messages exchanged after a successful handshake. Underscore characters separate the parts of the cipher suite. Here is a summary of the parts:

The Google web server sent two digital certificates during the mutual challenge phase. Each is an X.509 certificate generated with the RSA algorithm, and each of the SHA fingerprints is 160 bits in length. The format of each certificate follows the X.509 specification.

How does the GoogleClient verify the two X.509 certificates the Google web server sends? There is nothing in the GoogleClient code to suggest certificate verification. The core Java JDK ships with a default truststore (JAVA_HOME/jre/lib/security/cacerts). Because the GoogleClient opens an HTTPS connection to the Google web server, the Java runtime intervenes to handle the certificate verification, and because Google is such a standard website, the cacerts truststore has entries to verify the Google certificates. What about HTTPS connections to less popular and prominent sites? Java’s security API makes it possible to disable peer authentication so that a client such as the GoogleClient can take over certificate verification and make its own decisions about what to accept and reject. The GoogleTrustingClient (see Example 6-2) revises the GoogleClient and shows how programmatic verification can be done—or bypassed altogether.

Example 6-2. The GoogleTrustingClient, which turns off certificate verification

import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.net.MalformedURLException;
import java.security.cert.Certificate;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;

class GoogleTrustingClient {
    private static final String endpoint = "https://www.google.com:443/";

    public static void main(String[ ] args) {
        new GoogleTrustingClient().doIt();
    }
    private void doIt() {
        try {
            // Configure the HttpsURLConnection so that it does not
            // check certificates.
            SSLContext sslCtx = SSLContext.getInstance("TLS");              1
            TrustManager[ ] trustMgr = getTrustMgr();                       2
            sslCtx.init(null,                // key manager                 3
                        trustMgr,            // trust manager               4
                        new SecureRandom()); // random number generator     5
            HttpsURLConnection.setDefaultSSLSocketFactory(
                                     sslCtx.getSocketFactory());            6

            URL url = new URL(endpoint);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setDoInput(true);
            conn.setRequestMethod("GET");
            conn.connect();
            dumpDetails(conn);
        }
        catch(MalformedURLException e) { System.err.println(e); }
        catch(IOException e) { System.err.println(e); }
        catch(Exception e) { System.err.println(e); }
    }
    private TrustManager[ ] getTrustMgr() {                                 7
        // No exceptions thrown in any of the methods.
        TrustManager[ ] certs = new TrustManager[ ] {
            new X509TrustManager() {
                public X509Certificate[ ] getAcceptedIssuers() {            8
                    return null;
                }
                public void checkClientTrusted(X509Certificate[ ] certs,      9
                                               String type) { }
                public void checkServerTrusted(X509Certificate[ ] certs,      10
                                               String type) { }
            }
        };
        return certs;
    }
    private void dumpDetails(HttpsURLConnection conn) {
        try {
            print("Status code:  " + conn.getResponseCode());
            print("Cipher suite: " + conn.getCipherSuite());
            Certificate[ ] certs = conn.getServerCertificates();
            for (Certificate cert : certs) {
                print("\tCert. type: " + cert.getType());
                print("\tHash code:  " + cert.hashCode());
                print("\tAlgorithm:  " + cert.getPublicKey().getAlgorithm());
                print("\tFormat:     " + cert.getPublicKey().getFormat());
                print("");
            }
        }
        catch(Exception e) { System.err.println(e); }
    }
    private void print(String s) { System.out.println(s); }
}

The GoogleTrustingClient first gets an instance of an SSLContext (line 1) and then invokes getTrustManager (line 2) to get a TrustManager[ ], an array of managers for the in-memory truststore. A TrustManager defines three methods:

Each method is minimally defined but—and this is the critical point—no method throws a CertificateException, which means that the TrustManager effectively accepts all certificates. The TrustManager array returned from the getTrustManager method (line 7) has a single member, and this member is all-trusting. The result is that GoogleTrustingClient effectively turns off certificate verification, a move that can be useful during development. In production, the TrustManager could implement whatever certificate-inspection logic is appropriate.

The security details about HTTPS can be examined concretely with a lightweight HTTPS server, introduced in the next section. The Endpoint utility class, used in earlier chapters to publish both REST-style and SOAP-based services, does not support HTTPS connections. The Endpoint publisher is built with the HttpServer class, which ships with core Java. The next section uses the related class HttpsServer to clarify, with code, how HTTPS works.