* commit '16bdd13ec7e64c04e1a17b94ffcb2a08f3b47518': docs: add bdc's security doc as training article
This commit is contained in:
539
docs/html/training/articles/security-ssl.jd
Normal file
539
docs/html/training/articles/security-ssl.jd
Normal file
@@ -0,0 +1,539 @@
|
||||
page.title=Security with HTTPS and SSL
|
||||
page.article=true
|
||||
@jd:body
|
||||
|
||||
<div id="tb-wrapper">
|
||||
<div id="tb">
|
||||
<h2>In this document</h2>
|
||||
<ol class="nolist">
|
||||
<li><a href="#Concepts">Concepts</a></li>
|
||||
<li><a href="#HttpsExample">An HTTP Example</a></li>
|
||||
<li><a href="#CommonProblems">Common Problems Verifying Server Certificates</a>
|
||||
<ol class="nolist">
|
||||
<li><a href="#UnknownCa">Unknown certificate authority</a></li>
|
||||
<li><a href="#SelfSigned">Self-signed server certificate</a></li>
|
||||
<li><a href="#MissingCa">Missing intermediate certificate authority</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><a href="#CommonHostnameProbs">Common Problems with Hostname Verification</a></li>
|
||||
<li><a href="#WarningsSslSocket">Warnings About Using SSLSocket Directly</a></li>
|
||||
<li><a href="#Blacklisting">Blacklisting</a></li>
|
||||
<li><a href="#Pinning">Pinning</a></li>
|
||||
<li><a href="#ClientCert">Client Certificates</a></li>
|
||||
</ol>
|
||||
|
||||
|
||||
<h2>See also</h2>
|
||||
<ul>
|
||||
<li><a href="http://source.android.com/tech/security/index.html">Android
|
||||
Security Overview</a></li>
|
||||
<li><a href="{@docRoot}guide/topics/security/permissions.html">Permissions</a></li>
|
||||
</ul>
|
||||
</div></div>
|
||||
|
||||
|
||||
|
||||
<p>The Secure Sockets Layer (SSL)—now technically known as <a
|
||||
href="http://en.wikipedia.org/wiki/Transport_Layer_Security">Transport Layer Security
|
||||
(TLS)</a>—is a
|
||||
common building block for encrypted communications between clients and servers. It's possible that
|
||||
an application might use SSL incorrectly such that malicious entities may
|
||||
be able to intercept an app's data over the network. To help you ensure that this does not happen
|
||||
to your app, this article highlights the common pitfalls when using secure network protocols and addresses some larger concerns about using <a
|
||||
href="http://en.wikipedia.org/wiki/Public-key_infrastructure">Public-Key Infrastructure (PKI)</a>.
|
||||
|
||||
|
||||
<h2 id="Concepts">Concepts</h2>
|
||||
|
||||
<p>In a typical SSL usage scenario, a server is configured with a certificate containing a
|
||||
public key as well as a matching private key. As part of the handshake between an SSL client
|
||||
and server, the server proves it has the private key by signing its certificate with <a
|
||||
href="http://en.wikipedia.org/wiki/Public-key_cryptography">public-key cryptography</a>.</p>
|
||||
|
||||
<p>However, anyone can generate their own certificate and private key, so a simple handshake
|
||||
doesn't prove anything about the server other than that the server knows the private key that
|
||||
matches the public key of the certificate. One way to solve this problem is to have the client
|
||||
have a set of one or more certificates it trusts. If the certificate is not in the set, the
|
||||
server is not to be trusted.</p>
|
||||
|
||||
<p>There are several downsides to this simple approach. Servers should be able to
|
||||
upgrade to stronger keys over time ("key rotation"), which replaces the public key in the
|
||||
certificate with a new one. Unfortunately, now the client app has to be updated due to what
|
||||
is essentially a server configuration change. This is especially problematic if the server
|
||||
is not under the app developer's control, for example if it is a third party web service. This
|
||||
approach also has issues if the app has to talk to arbitrary servers such as a web browser or
|
||||
email app.</p>
|
||||
|
||||
<p>In order to address these downsides, servers are typically configured with certificates
|
||||
from well known issuers called <a
|
||||
href="http://en.wikipedia.org/wiki/Certificate_authority">Certificate Authorities (CAs)</a>.
|
||||
The host platform generally contains a list of well known CAs that it trusts.
|
||||
As of Android 4.2 (Jelly Bean), Android currently contains over 100 CAs that are updated
|
||||
in each release. Similar to a server, a CA has a certificate and a private key. When issuing
|
||||
a certificate for a server, the CA <a
|
||||
href="http://en.wikipedia.org/wiki/Digital_signature">signs</a>
|
||||
the server certificate using its private key. The
|
||||
client can then verify that the server has a certificate issued by a CA known to the platform.</p>
|
||||
|
||||
<p>However, while solving some problems, using CAs introduces another. Because the CA issues
|
||||
certificates for many servers, you still need some way to make sure you are talking to the
|
||||
server you want. To address this, the certificate issued by the CA identifies the server
|
||||
either with a specific name such as <em>gmail.com</em> or a wildcarded set of
|
||||
hosts such as <em>*.google.com</em>. </p>
|
||||
|
||||
<p>The following example will make these concepts a little more concrete. In the snippet below
|
||||
from a command line, the <a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a>
|
||||
tool's {@code s_client} command looks at Wikipedia's server certificate information. It
|
||||
specifies port 443 because that is the default for <acronym title="Hypertext Transfer
|
||||
Protocol Secure">HTTPS</acronym>. The command sends
|
||||
the output of {@code openssl s_client} to {@code openssl x509}, which formats information
|
||||
about certificates according to the <a
|
||||
href="http://en.wikipedia.org/wiki/X.509">X.509 standard</a>. Specifically,
|
||||
the command asks for the subject, which contains the server name information,
|
||||
and the issuer, which identifies the CA.</p>
|
||||
|
||||
<pre class="no-pretty-print">
|
||||
$ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
|
||||
<b>subject=</b> /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/<b>CN=*.wikipedia.org</b>
|
||||
<b>issuer=</b> /C=US/O=GeoTrust, Inc./CN=<b>RapidSSL CA</b>
|
||||
</pre>
|
||||
|
||||
<p>You can see that the certificate was issued for servers matching <em>*.wikipedia.org</em> by
|
||||
the RapidSSL CA.</p>
|
||||
|
||||
|
||||
|
||||
<h2 id="HttpsExample">An HTTPS Example</h2>
|
||||
|
||||
<p>Assuming you have a web server with a
|
||||
certificate issued by a well known CA, you can make a secure request with code as
|
||||
simple this:</p>
|
||||
|
||||
<pre>
|
||||
URL url = new URL("https://wikipedia.org");
|
||||
URLConnection urlConnection = url.openConnection();
|
||||
InputStream in = urlConnection.getInputStream();
|
||||
copyInputStreamToOutputStream(in, System.out);
|
||||
</pre>
|
||||
|
||||
<p>Yes, it really can be that simple. If you want to tailor the HTTP request, you can cast to
|
||||
an {@link java.net.HttpURLConnection}. The Android documentation for
|
||||
{@link java.net.HttpURLConnection} has further examples about how to deal with request
|
||||
and response headers, posting content, managing cookies, using proxies, caching responses,
|
||||
and so on. But in terms of the details for verifying certificates and hostnames, the Android
|
||||
framework takes care of it for you through these APIs.
|
||||
This is where you want to be if at all possible. That said, below are some other considerations.</p>
|
||||
|
||||
|
||||
|
||||
<h2 id="CommonProblems">Common Problems Verifying Server Certificates</h2>
|
||||
|
||||
<p>Suppose instead of receiving the content from {@link java.net.URLConnection#getInputStream
|
||||
getInputStream()}, it throws an exception:</p>
|
||||
|
||||
<pre class="no-pretty-print">
|
||||
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
|
||||
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
|
||||
at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
|
||||
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
|
||||
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
|
||||
at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
|
||||
at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
|
||||
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
|
||||
at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
|
||||
at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
|
||||
</pre>
|
||||
|
||||
<p>This can happen for several reasons, including:
|
||||
<ol>
|
||||
<li><a href="#UnknownCa">The CA that issued the server certificate was unknown</a></li>
|
||||
<li><a href="#SelfSigned">The server certificate wasn't signed by a CA, but was self signed</a></li>
|
||||
<li><a href="#MissingCa">The server configuration is missing an intermediate CA</a></li>
|
||||
</ol>
|
||||
|
||||
<p>The following sections discuss how to address these problems while keeping your
|
||||
connection to the server secure.
|
||||
|
||||
|
||||
|
||||
<h3 id="UnknownCa">Unknown certificate authority</h3>
|
||||
|
||||
<p>In this case, the {@link javax.net.ssl.SSLHandshakeException} occurs
|
||||
because you have a CA that isn't trusted by the system. It could be because
|
||||
you have a certificate from a new CA that isn't yet trusted by Android or your app is
|
||||
running on an older version without the CA. More often a CA is unknown because it isn't a
|
||||
public CA, but a private one issued by an organization such as a government, corporation,
|
||||
or education institution for their own use.</p>
|
||||
|
||||
<p>Fortunately, you can teach {@link javax.net.ssl.HttpsURLConnection}
|
||||
to trust a specific set of CAs. The procedure
|
||||
can be a little convoluted, so below is an example that takes a specific CA from
|
||||
an {@link java.io.InputStream}, uses it to create a {@link java.security.KeyStore},
|
||||
which is then used to create and initialize a
|
||||
{@link javax.net.ssl.TrustManager}. A {@link javax.net.ssl.TrustManager} is what the system
|
||||
uses to validate certificates from the server
|
||||
and—by creating one from a {@link java.security.KeyStore} with one or more CAs—those
|
||||
will be the only CAs trusted by that {@link javax.net.ssl.TrustManager}.</p>
|
||||
|
||||
<p>Given the new {@link javax.net.ssl.TrustManager},
|
||||
the example initializes a new {@link javax.net.ssl.SSLContext} which provides
|
||||
an {@link javax.net.ssl.SSLSocketFactory} you can use to override the default
|
||||
{@link javax.net.ssl.SSLSocketFactory} from
|
||||
{@link javax.net.ssl.HttpsURLConnection}. This way the
|
||||
connection will use your CAs for certificate validation.</p>
|
||||
|
||||
<p>Here is the example in
|
||||
full using an organizational CA from the University of Washington:</p>
|
||||
|
||||
<pre>
|
||||
// Load CAs from an InputStream
|
||||
// (could be from a resource or ByteArrayInputStream or ...)
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
|
||||
InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
|
||||
Certificate ca;
|
||||
try {
|
||||
ca = cf.generateCertificate(caInput);
|
||||
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
|
||||
} finally {
|
||||
caInput.close();
|
||||
}
|
||||
|
||||
// Create a KeyStore containing our trusted CAs
|
||||
String keyStoreType = KeyStore.getDefaultType();
|
||||
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
|
||||
keyStore.load(null, null);
|
||||
keyStore.setCertificateEntry("ca", ca);
|
||||
|
||||
// Create a TrustManager that trusts the CAs in our KeyStore
|
||||
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
|
||||
tmf.init(keyStore);
|
||||
|
||||
// Create an SSLContext that uses our TrustManager
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, tmf.getTrustManagers(), null);
|
||||
|
||||
// Tell the URLConnection to use a SocketFactory from our SSLContext
|
||||
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
|
||||
HttpsURLConnection urlConnection =
|
||||
(HttpsURLConnection)url.openConnection();
|
||||
urlConnection.setSSLSocketFactory(context.getSocketFactory());
|
||||
InputStream in = urlConnection.getInputStream();
|
||||
copyInputStreamToOutputStream(in, System.out);
|
||||
</pre>
|
||||
|
||||
<p>With a custom {@link javax.net.ssl.TrustManager} that knows about your CAs,
|
||||
the system is able to validate
|
||||
that your server certificate come from a trusted issuer.</p>
|
||||
|
||||
<p class="caution"><strong>Caution:</strong>
|
||||
Many web sites describe a poor alternative solution which is to install a
|
||||
{@link javax.net.ssl.TrustManager} that does nothing. If you do this you might as well not
|
||||
be encrypting your communication, because anyone can attack your users at a public Wi-Fi hotspot
|
||||
by using <acronym title="Domain Name System">DNS</acronym> tricks to send your users'
|
||||
traffic through a proxy of their own that pretends to be your server. The attacker can then
|
||||
record passwords and other personal data. This works because the attacker can generate a
|
||||
certificate and—without a {@link javax.net.ssl.TrustManager} that actually
|
||||
validates that the certificate comes from a trusted
|
||||
source—your app could be talking to anyone. So don't do this, not even temporarily. You can
|
||||
always make your app trust the issuer of the server's certificate, so just do it.</p>
|
||||
|
||||
|
||||
|
||||
<h3 id="SelfSigned">Self-signed server certificate</h3>
|
||||
|
||||
<p>The second case of {@link javax.net.ssl.SSLHandshakeException} is
|
||||
due to a self-signed certificate, which means the server is behaving as its own CA.
|
||||
This is similar to an unknown certificate authority, so you can use the
|
||||
same approach from the previous section.</p>
|
||||
|
||||
<p>You can create yout own {@link javax.net.ssl.TrustManager},
|
||||
this time trusting the server certificate directly. This has all of the
|
||||
downsides discussed earlier of tying your app directly to a certificate, but can be done
|
||||
securely. However, you should be careful to make sure your self-signed certificate has a
|
||||
reasonably strong key. As of 2012, a 2048-bit RSA signature with an exponent of 65537 expiring
|
||||
yearly is acceptable. When rotating keys, you should check for <a
|
||||
href="http://csrc.nist.gov/groups/ST/key_mgmt/index.html">recommendations</a> from an
|
||||
authority (such as <a href="http://www.nist.gov/">NIST</a>) about what is acceptable.</p>
|
||||
|
||||
|
||||
|
||||
<h3 id="MissingCa">Missing intermediate certificate authority</h3>
|
||||
|
||||
<p>The third case of {@link javax.net.ssl.SSLHandshakeException}
|
||||
occurs due to a missing intermediate CA. Most public
|
||||
CAs don't sign server certificates directly. Instead, they use their main CA certificate,
|
||||
referred to as the root CA, to sign intermediate CAs. They do this so the root CA can be stored
|
||||
offline to reduce risk of compromise. However, operating systems like Android typically
|
||||
trust only root CAs directly, which leaves a short gap of trust between the server
|
||||
certificate—signed by the intermediate CA—and the certificate verifier,
|
||||
which knows the root CA. To solve
|
||||
this, the server doesn't send the client only it's certificate during the SSL handshake, but
|
||||
a chain of certificates from the server CA through any intermediates necessary to reach a
|
||||
trusted root CA.</p>
|
||||
|
||||
<p>To see what this looks like in practice, here's the <em>mail.google.com</em> certificate
|
||||
chain as viewed by the <a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a>
|
||||
{@code s_client} command:</p>
|
||||
|
||||
<pre class="no-pretty-print">
|
||||
$ openssl s_client -connect mail.google.com:443
|
||||
---
|
||||
Certificate chain
|
||||
0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com
|
||||
i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
|
||||
1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
|
||||
i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
|
||||
---
|
||||
</pre>
|
||||
|
||||
|
||||
<p>This shows that the server sends a certificate for <em>mail.google.com</em>
|
||||
issued by the <em>Thawte SGC</em> CA, which is an intermediate CA, and a second certificate
|
||||
for the <em>Thawte SGC</em> CA issued by a <em>Verisign</em> CA, which is the primary CA that's
|
||||
trusted by Android.</p>
|
||||
|
||||
<p>However, it is not uncommon to configure a server to not include the necessary
|
||||
intermediate CA. For example, here is a server that can cause an error in Android browsers and
|
||||
exceptions in Android apps:</p>
|
||||
|
||||
<pre class="no-pretty-print">
|
||||
$ openssl s_client -connect egov.uscis.gov:443
|
||||
---
|
||||
Certificate chain
|
||||
0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov
|
||||
i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3
|
||||
---
|
||||
</pre>
|
||||
|
||||
<p>What is interesting to note here is that visiting this server in most desktop browsers
|
||||
does not cause an error like a completely unknown CA or self-signed server certificate would
|
||||
cause. This is because most desktop browsers cache trusted intermediate CAs over time. Once
|
||||
a browser has visited and learned about an intermediate CA from one site, it won't
|
||||
need to have the intermediate CA included in the certificate chain the next time.</p>
|
||||
|
||||
<p>Some sites do this intentionally for secondary web servers used to serve resources. For
|
||||
example, they might have their main HTML page served by a server with a full certificate
|
||||
chain, but have servers for resources such as images, CSS, or JavaScript not include the
|
||||
CA, presumably to save bandwidth. Unfortunately, sometimes these servers might be providing
|
||||
a web service you are trying to call from your Android app, which is not as forgiving.</p>
|
||||
|
||||
<p>There are two approaches to solve this issue:</p>
|
||||
<ul>
|
||||
<li>Configure the server to
|
||||
include the intermediate CA in the server chain. Most CAs provide documentation on how to do
|
||||
this for all common web servers. This is the only approach if you need the site to work with
|
||||
default Android browsers at least through Android 4.2.</li>
|
||||
<li>Or, treat the
|
||||
intermediate CA like any other unknown CA, and create a {@link javax.net.ssl.TrustManager}
|
||||
to trust it directly, as done in the previous two sections.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h2 id="CommonHostnameProbs">Common Problems with Hostname Verification</h2>
|
||||
|
||||
<p>As mentioned at the beginning of this article,
|
||||
there are two key parts to verifying an SSL connection. The first
|
||||
is to verify the certificate is from a trusted source, which was the focus of the previous
|
||||
section. The focus of this section is the second part: making sure the server you are
|
||||
talking to presents the right certificate. When it doesn't, you'll typically see an error
|
||||
like this:</p>
|
||||
|
||||
<pre class="no-pretty-print">
|
||||
java.io.IOException: Hostname 'example.com' was not verified
|
||||
at libcore.net.http.HttpConnection.verifySecureSocketHostname(HttpConnection.java:223)
|
||||
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:446)
|
||||
at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
|
||||
at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
|
||||
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
|
||||
at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
|
||||
at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
|
||||
</pre>
|
||||
|
||||
|
||||
<p>One reason this can happen is due to a server configuration error. The server is
|
||||
configured with a certificate that does not have a subject or subject alternative name fields
|
||||
that match the server you are trying to reach. It is possible to have one certificate be used
|
||||
with many different servers. For example, looking at the <em>google.com</em> certificate with
|
||||
<a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a> {@code
|
||||
s_client -connect google.com:443 | openssl x509 -text} you can see that a subject
|
||||
that supports <em>*.google.com</em> but also subject alternative names for <em>*.youtube.com</em>,
|
||||
<em>*.android.com</em>, and others. The error occurs only when the server name you
|
||||
are connecting to isn't listed by the certificate as acceptable.</p>
|
||||
|
||||
<p>Unfortunately this can happen for another reason as well: <a
|
||||
href="http://en.wikipedia.org/wiki/Virtual_hosting">virtual hosting</a>. When sharing a
|
||||
server for more than one hostname with HTTP, the web server can tell from the HTTP/1.1 request
|
||||
which target hostname the client is looking for. Unfortunately this is complicated with
|
||||
HTTPS, because the server has to know which certificate to return before it sees the HTTP
|
||||
request. To address this problem, newer versions of SSL, specifically TLSv.1.0 and later,
|
||||
support <a href="http://en.wikipedia.org/wiki/Server_Name_Indication">Server Name Indication
|
||||
(SNI)</a>, which allows the SSL client to specify the intended
|
||||
hostname to the server so the proper certificate can be returned.</p>
|
||||
|
||||
<p>Fortunately, {@link javax.net.ssl.HttpsURLConnection} supports
|
||||
SNI since Android 2.3. Unfortunately, Apache
|
||||
HTTP Client does not, which is one of the many reasons we discourage its use. One workaround
|
||||
if you need to support Android 2.2 (and older) or Apache HTTP Client is to set up an alternative
|
||||
virtual host on a unique port so that it's unambiguous which server certificate to return.</p>
|
||||
|
||||
<p>The more drastic alternative is to replace {@link javax.net.ssl.HostnameVerifier}
|
||||
with one that uses not the
|
||||
hostname of your virtual host, but the one returned by the server by default.</p>
|
||||
|
||||
<p class="caution"><strong>Caution:</strong> Replacing {@link javax.net.ssl.HostnameVerifier}
|
||||
can be <strong>very dangerous</strong> if the other virtual host is
|
||||
not under your control, because a man-in-the-middle attack could direct traffic to another
|
||||
server without your knowledge.</p>
|
||||
|
||||
<p>If you are still sure you want to override hostname verification, here is an example
|
||||
that replaces the verifier for a single {@link java.net.URLConnection}
|
||||
with one that still verifies that the hostname is at least on expected by the app:</p>
|
||||
|
||||
<pre>
|
||||
// Create an HostnameVerifier that hardwires the expected hostname.
|
||||
// Note that is different than the URL's hostname:
|
||||
// example.com versus example.org
|
||||
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
HostnameVerifier hv =
|
||||
HttpsURLConnection.getDefaultHostnameVerifier();
|
||||
return hv.verify("example.com", session);
|
||||
}
|
||||
};
|
||||
|
||||
// Tell the URLConnection to use our HostnameVerifier
|
||||
URL url = new URL("https://example.org/");
|
||||
HttpsURLConnection urlConnection =
|
||||
(HttpsURLConnection)url.openConnection();
|
||||
urlConnection.setHostnameVerifier(hostnameVerifier);
|
||||
InputStream in = urlConnection.getInputStream();
|
||||
copyInputStreamToOutputStream(in, System.out);
|
||||
</pre>
|
||||
|
||||
<p>But remember, if you find yourself replacing hostname verification, especially
|
||||
due to virtual hosting, it's still <strong>very dangerous</strong> if the other virtual host is
|
||||
not under your control and you should find an alternative hosting arrangement
|
||||
that avoids this issue.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
<h2 id="WarningsSslSocket">Warnings About Using SSLSocket Directly</h2>
|
||||
|
||||
<p>So far, the examples have focused on HTTPS using {@link javax.net.ssl.HttpsURLConnection}.
|
||||
Sometimes apps need to use SSL separate from HTTP. For example, an email app might use SSL variants
|
||||
of SMTP, POP3, or IMAP. In those cases, the app would want to use {@link javax.net.ssl.SSLSocket}
|
||||
directly, much the same way that {@link javax.net.ssl.HttpsURLConnection} does internally.</p>
|
||||
|
||||
<p>The techniques described so
|
||||
far to deal with certificate verification issues also apply to {@link javax.net.ssl.SSLSocket}.
|
||||
In fact, when using a custom {@link javax.net.ssl.TrustManager}, what is passed to
|
||||
{@link javax.net.ssl.HttpsURLConnection} is an {@link javax.net.ssl.SSLSocketFactory}.
|
||||
So if you need to use a custom {@link javax.net.ssl.TrustManager} with an
|
||||
{@link javax.net.ssl.SSLSocket}, follow
|
||||
the same steps and use that {@link javax.net.ssl.SSLSocketFactory} to create your
|
||||
{@link javax.net.ssl.SSLSocket}.</p>
|
||||
|
||||
<p class="caution"><strong>Caution:</strong>
|
||||
{@link javax.net.ssl.SSLSocket} <strong>does not</strong> perform hostname verification. It is
|
||||
up the your app to do its own hostname verification, preferably by calling {@link
|
||||
javax.net.ssl.HttpsURLConnection#getDefaultHostnameVerifier()} with the expected hostname. Further
|
||||
beware that {@link javax.net.ssl.HostnameVerifier#verify HostnameVerifier.verify()}
|
||||
doesn't throw an exception on error but instead returns a boolean result that you must
|
||||
explicitly check.</p>
|
||||
|
||||
<p>Here is an example showing how you can do this. It shows that when connecting to
|
||||
<em>gmail.com</em> port 443 without SNI support, you'll receive a certificate for
|
||||
<em>mail.google.com</em>. This is expected in this case, so check to make sure that
|
||||
the certificate is indeed for <em>mail.google.com</em>:</p>
|
||||
|
||||
<pre>
|
||||
// Open SSLSocket directly to gmail.com
|
||||
SocketFactory sf = SSLSocketFactory.getDefault();
|
||||
SSLSocket socket = (SSLSocket) sf.createSocket("gmail.com", 443);
|
||||
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
|
||||
SSLSession s = socket.getSession();
|
||||
|
||||
// Verify that the certicate hostname is for mail.google.com
|
||||
// This is due to lack of SNI support in the current SSLSocket.
|
||||
if (!hv.verify("mail.google.com", s)) {
|
||||
throw new SSLHandshakeException("Expected mail.google.com, "
|
||||
"found " + s.getPeerPrincipal());
|
||||
}
|
||||
|
||||
// At this point SSLSocket performed certificate verificaiton and
|
||||
// we have performed hostname verification, so it is safe to proceed.
|
||||
|
||||
// ... use socket ...
|
||||
socket.close();
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
<h2 id="Blacklisting">Blacklisting</h2>
|
||||
|
||||
<p>SSL relies heavily on CAs to issue certificates to only the properly verified owners
|
||||
of servers and domains. In rare cases, CAs are either tricked or, in the case of <a
|
||||
href="http://en.wikipedia.org/wiki/Comodo_Group#Breach_of_security">Comodo</a> or <a
|
||||
href="http://en.wikipedia.org/wiki/DigiNotar">DigiNotar</a>, breached,
|
||||
resulting in the certificates for a hostname to be issued to
|
||||
someone other than the owner of the server or domain.</p>
|
||||
|
||||
<p>In order to mitigate this risk, Android has the ability to blacklist certain certificates or even
|
||||
whole CAs. While this list was historically built into the operating system, starting in
|
||||
Android 4.2 this list can be remotely updated to deal with future compromises.</p>
|
||||
|
||||
|
||||
|
||||
<h2 id="Pinning">Pinning</h2>
|
||||
|
||||
<p>An app can further protect itself from fraudulently issued certificates by a
|
||||
technique known as pinning. This is basically using the example provided in the unknown CA case
|
||||
above to restrict an app's trusted CAs to a small set known to be used by the app's servers. This
|
||||
prevents the compromise of one of the other 100+ CAs in the system from resulting in a breach of
|
||||
the apps secure channel.</p>
|
||||
|
||||
|
||||
|
||||
<h2 id="ClientCert">Client Certificates</h2>
|
||||
|
||||
<p>This article has focused on the user of SSL to secure communications with servers. SSL also
|
||||
supports the notion of client certificates that allow the server to validate the identity of a
|
||||
client. While beyond the scope of this article, the techniques involved are similar to specifying
|
||||
a custom {@link javax.net.ssl.TrustManager}.
|
||||
See the discussion about creating a custom {@link javax.net.ssl.KeyManager} in the documentation for
|
||||
{@link javax.net.ssl.HttpsURLConnection}.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1069,6 +1069,13 @@
|
||||
"How to perform various tasks and keep your app's data and your user's data secure."
|
||||
>Security Tips</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="<?cs var:toroot ?>training/articles/security-ssl.html"
|
||||
description=
|
||||
"How to ensure that your app is secure when performing network transactions."
|
||||
>Security with HTTPS and SSL</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-section">
|
||||
<div class="nav-section-header">
|
||||
|
||||
Reference in New Issue
Block a user