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:
Authorization
header (or the query string).
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 browser checks the web server’s digital certificates against the browser’s truststore, which is a database of digital certificates that the browser trusts. The browser’s validation of an incoming certificate can be and, for practical reasons, typically is indirect. For example, suppose that the browser receives a certificate from Amazon but does not have an Amazon certificate in its truststore. Suppose further that the Amazon certificate contains a vouching signature from VeriSign, a well-known certificate authority (CA). If the browser’s truststore has a VeriSign certificate, then the browser can use the VeriSign certificate to validate the VeriSign signature on the Amazon certificate.
How is the VeriSign certificate to be verified? For the verification process to terminate, at least one certificate in the chain must be taken as verified. For now, the point of interest is that the browser’s truststore is its repository of certificates that are used to verify incoming certificates. If the browser cannot validate an incoming certificate against its truststore, then the browser typically asks the human user whether the certificate should be trusted this time only or permanently. If the user selects permanently, the browser adds the certificate to its truststore.
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"
![]()
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.
Example 6-1. A client that makes an HTTPS connection to Google
import
java.net.URL
;
import
javax.net.ssl.HttpsURLConnection
;
import
java.net.MalformedURLException
;
import
java.security.cert.Certificate
;
import
java.io.IOException
;
import
java.io.BufferedReader
;
import
java.io.InputStreamReader
;
public
class
GoogleClient
{
private
static
final
String
endpoint
=
"https://www.google.com:443/"
;
![]()
// Send a GET request and print the response status code.
public
static
void
main
(
String
[
]
args
)
{
new
GoogleClient
().
doIt
();
}
private
void
doIt
()
{
try
{
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
);
}
}
private
void
dumpDetails
(
HttpsURLConnection
conn
)
{
try
{
(
"Status code: "
+
conn
.
getResponseCode
());
![]()
(
"Cipher suite: "
+
conn
.
getCipherSuite
());
![]()
Certificate
[
]
certs
=
conn
.
getServerCertificates
();
![]()
for
(
Certificate
cert
:
certs
)
{
(
"\tCert. type: "
+
cert
.
getType
());
(
"\tHash code: "
+
cert
.
hashCode
());
![]()
(
"\tAlgorithm: "
+
cert
.
getPublicKey
().
getAlgorithm
());
![]()
(
"\tFormat: "
+
cert
.
getPublicKey
().
getFormat
());
![]()
(
""
);
}
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
e
);
}
}
private
void
(
Object
s
)
{
System
.
out
.
println
(
s
);
}
}
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
![]()
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"
);
![]()
TrustManager
[
]
trustMgr
=
getTrustMgr
();
![]()
sslCtx
.
init
(
null
,
// key manager
![]()
trustMgr
,
// trust manager
![]()
new
SecureRandom
());
// random number generator
![]()
HttpsURLConnection
.
setDefaultSSLSocketFactory
(
sslCtx
.
getSocketFactory
());
![]()
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
()
{
![]()
// No exceptions thrown in any of the methods.
TrustManager
[
]
certs
=
new
TrustManager
[
]
{
new
X509TrustManager
()
{
public
X509Certificate
[
]
getAcceptedIssuers
()
{
![]()
return
null
;
}
public
void
checkClientTrusted
(
X509Certificate
[
]
certs
,
![]()
String
type
)
{
}
public
void
checkServerTrusted
(
X509Certificate
[
]
certs
,
![]()
String
type
)
{
}
}
};
return
certs
;
}
private
void
dumpDetails
(
HttpsURLConnection
conn
)
{
try
{
(
"Status code: "
+
conn
.
getResponseCode
());
(
"Cipher suite: "
+
conn
.
getCipherSuite
());
Certificate
[
]
certs
=
conn
.
getServerCertificates
();
for
(
Certificate
cert
:
certs
)
{
(
"\tCert. type: "
+
cert
.
getType
());
(
"\tHash code: "
+
cert
.
hashCode
());
(
"\tAlgorithm: "
+
cert
.
getPublicKey
().
getAlgorithm
());
(
"\tFormat: "
+
cert
.
getPublicKey
().
getFormat
());
(
""
);
}
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
e
);
}
}
private
void
(
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:
getAcceptedIssuers
X509Certificate
instances. In this case,
null
is returned, which is explained shortly.
checkClientTrusted
X509Certificate
instances and, as a second argument, a String
that describes the certificate’s type (for
instance, X509). The method has void
as the return type.
checkServerTrusted
checkClientTrusted
.
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.