Scott Main | 0015836 | 2013-01-17 16:58:50 -0800 | [diff] [blame] | 1 | page.title=Security with HTTPS and SSL |
Scott Main | 1c2dea0 | 2013-04-10 18:59:29 -0700 | [diff] [blame] | 2 | page.tags="network","certificates" |
| 3 | |
Scott Main | 0015836 | 2013-01-17 16:58:50 -0800 | [diff] [blame] | 4 | page.article=true |
| 5 | @jd:body |
| 6 | |
| 7 | <div id="tb-wrapper"> |
| 8 | <div id="tb"> |
| 9 | <h2>In this document</h2> |
| 10 | <ol class="nolist"> |
| 11 | <li><a href="#Concepts">Concepts</a></li> |
| 12 | <li><a href="#HttpsExample">An HTTP Example</a></li> |
| 13 | <li><a href="#CommonProblems">Common Problems Verifying Server Certificates</a> |
| 14 | <ol class="nolist"> |
| 15 | <li><a href="#UnknownCa">Unknown certificate authority</a></li> |
| 16 | <li><a href="#SelfSigned">Self-signed server certificate</a></li> |
| 17 | <li><a href="#MissingCa">Missing intermediate certificate authority</a></li> |
| 18 | </ol> |
| 19 | </li> |
| 20 | <li><a href="#CommonHostnameProbs">Common Problems with Hostname Verification</a></li> |
| 21 | <li><a href="#WarningsSslSocket">Warnings About Using SSLSocket Directly</a></li> |
| 22 | <li><a href="#Blacklisting">Blacklisting</a></li> |
| 23 | <li><a href="#Pinning">Pinning</a></li> |
| 24 | <li><a href="#ClientCert">Client Certificates</a></li> |
| 25 | </ol> |
| 26 | |
| 27 | |
| 28 | <h2>See also</h2> |
| 29 | <ul> |
| 30 | <li><a href="http://source.android.com/tech/security/index.html">Android |
| 31 | Security Overview</a></li> |
| 32 | <li><a href="{@docRoot}guide/topics/security/permissions.html">Permissions</a></li> |
| 33 | </ul> |
| 34 | </div></div> |
| 35 | |
| 36 | |
| 37 | |
| 38 | <p>The Secure Sockets Layer (SSL)—now technically known as <a |
| 39 | href="http://en.wikipedia.org/wiki/Transport_Layer_Security">Transport Layer Security |
| 40 | (TLS)</a>—is a |
| 41 | common building block for encrypted communications between clients and servers. It's possible that |
| 42 | an application might use SSL incorrectly such that malicious entities may |
| 43 | be able to intercept an app's data over the network. To help you ensure that this does not happen |
| 44 | to your app, this article highlights the common pitfalls when using secure network protocols and addresses some larger concerns about using <a |
| 45 | href="http://en.wikipedia.org/wiki/Public-key_infrastructure">Public-Key Infrastructure (PKI)</a>. |
| 46 | |
| 47 | |
| 48 | <h2 id="Concepts">Concepts</h2> |
| 49 | |
| 50 | <p>In a typical SSL usage scenario, a server is configured with a certificate containing a |
| 51 | public key as well as a matching private key. As part of the handshake between an SSL client |
| 52 | and server, the server proves it has the private key by signing its certificate with <a |
| 53 | href="http://en.wikipedia.org/wiki/Public-key_cryptography">public-key cryptography</a>.</p> |
| 54 | |
| 55 | <p>However, anyone can generate their own certificate and private key, so a simple handshake |
| 56 | doesn't prove anything about the server other than that the server knows the private key that |
| 57 | matches the public key of the certificate. One way to solve this problem is to have the client |
| 58 | have a set of one or more certificates it trusts. If the certificate is not in the set, the |
| 59 | server is not to be trusted.</p> |
| 60 | |
| 61 | <p>There are several downsides to this simple approach. Servers should be able to |
| 62 | upgrade to stronger keys over time ("key rotation"), which replaces the public key in the |
| 63 | certificate with a new one. Unfortunately, now the client app has to be updated due to what |
| 64 | is essentially a server configuration change. This is especially problematic if the server |
| 65 | is not under the app developer's control, for example if it is a third party web service. This |
| 66 | approach also has issues if the app has to talk to arbitrary servers such as a web browser or |
| 67 | email app.</p> |
| 68 | |
| 69 | <p>In order to address these downsides, servers are typically configured with certificates |
| 70 | from well known issuers called <a |
| 71 | href="http://en.wikipedia.org/wiki/Certificate_authority">Certificate Authorities (CAs)</a>. |
| 72 | The host platform generally contains a list of well known CAs that it trusts. |
| 73 | As of Android 4.2 (Jelly Bean), Android currently contains over 100 CAs that are updated |
| 74 | in each release. Similar to a server, a CA has a certificate and a private key. When issuing |
| 75 | a certificate for a server, the CA <a |
| 76 | href="http://en.wikipedia.org/wiki/Digital_signature">signs</a> |
| 77 | the server certificate using its private key. The |
| 78 | client can then verify that the server has a certificate issued by a CA known to the platform.</p> |
| 79 | |
| 80 | <p>However, while solving some problems, using CAs introduces another. Because the CA issues |
| 81 | certificates for many servers, you still need some way to make sure you are talking to the |
| 82 | server you want. To address this, the certificate issued by the CA identifies the server |
| 83 | either with a specific name such as <em>gmail.com</em> or a wildcarded set of |
| 84 | hosts such as <em>*.google.com</em>. </p> |
| 85 | |
| 86 | <p>The following example will make these concepts a little more concrete. In the snippet below |
| 87 | from a command line, the <a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a> |
| 88 | tool's {@code s_client} command looks at Wikipedia's server certificate information. It |
| 89 | specifies port 443 because that is the default for <acronym title="Hypertext Transfer |
| 90 | Protocol Secure">HTTPS</acronym>. The command sends |
| 91 | the output of {@code openssl s_client} to {@code openssl x509}, which formats information |
| 92 | about certificates according to the <a |
| 93 | href="http://en.wikipedia.org/wiki/X.509">X.509 standard</a>. Specifically, |
| 94 | the command asks for the subject, which contains the server name information, |
| 95 | and the issuer, which identifies the CA.</p> |
| 96 | |
| 97 | <pre class="no-pretty-print"> |
| 98 | $ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer |
| 99 | <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> |
| 100 | <b>issuer=</b> /C=US/O=GeoTrust, Inc./CN=<b>RapidSSL CA</b> |
| 101 | </pre> |
| 102 | |
| 103 | <p>You can see that the certificate was issued for servers matching <em>*.wikipedia.org</em> by |
| 104 | the RapidSSL CA.</p> |
| 105 | |
| 106 | |
| 107 | |
| 108 | <h2 id="HttpsExample">An HTTPS Example</h2> |
| 109 | |
| 110 | <p>Assuming you have a web server with a |
| 111 | certificate issued by a well known CA, you can make a secure request with code as |
| 112 | simple this:</p> |
| 113 | |
| 114 | <pre> |
| 115 | URL url = new URL("https://wikipedia.org"); |
| 116 | URLConnection urlConnection = url.openConnection(); |
| 117 | InputStream in = urlConnection.getInputStream(); |
| 118 | copyInputStreamToOutputStream(in, System.out); |
| 119 | </pre> |
| 120 | |
| 121 | <p>Yes, it really can be that simple. If you want to tailor the HTTP request, you can cast to |
| 122 | an {@link java.net.HttpURLConnection}. The Android documentation for |
| 123 | {@link java.net.HttpURLConnection} has further examples about how to deal with request |
| 124 | and response headers, posting content, managing cookies, using proxies, caching responses, |
| 125 | and so on. But in terms of the details for verifying certificates and hostnames, the Android |
| 126 | framework takes care of it for you through these APIs. |
| 127 | This is where you want to be if at all possible. That said, below are some other considerations.</p> |
| 128 | |
| 129 | |
| 130 | |
| 131 | <h2 id="CommonProblems">Common Problems Verifying Server Certificates</h2> |
| 132 | |
| 133 | <p>Suppose instead of receiving the content from {@link java.net.URLConnection#getInputStream |
| 134 | getInputStream()}, it throws an exception:</p> |
| 135 | |
| 136 | <pre class="no-pretty-print"> |
| 137 | javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found. |
| 138 | at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374) |
| 139 | at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209) |
| 140 | at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478) |
| 141 | at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433) |
| 142 | at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290) |
| 143 | at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240) |
| 144 | at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282) |
| 145 | at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177) |
| 146 | at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271) |
| 147 | </pre> |
| 148 | |
| 149 | <p>This can happen for several reasons, including: |
| 150 | <ol> |
| 151 | <li><a href="#UnknownCa">The CA that issued the server certificate was unknown</a></li> |
| 152 | <li><a href="#SelfSigned">The server certificate wasn't signed by a CA, but was self signed</a></li> |
| 153 | <li><a href="#MissingCa">The server configuration is missing an intermediate CA</a></li> |
| 154 | </ol> |
| 155 | |
| 156 | <p>The following sections discuss how to address these problems while keeping your |
| 157 | connection to the server secure. |
| 158 | |
| 159 | |
| 160 | |
| 161 | <h3 id="UnknownCa">Unknown certificate authority</h3> |
| 162 | |
| 163 | <p>In this case, the {@link javax.net.ssl.SSLHandshakeException} occurs |
| 164 | because you have a CA that isn't trusted by the system. It could be because |
| 165 | you have a certificate from a new CA that isn't yet trusted by Android or your app is |
| 166 | running on an older version without the CA. More often a CA is unknown because it isn't a |
| 167 | public CA, but a private one issued by an organization such as a government, corporation, |
| 168 | or education institution for their own use.</p> |
| 169 | |
| 170 | <p>Fortunately, you can teach {@link javax.net.ssl.HttpsURLConnection} |
| 171 | to trust a specific set of CAs. The procedure |
| 172 | can be a little convoluted, so below is an example that takes a specific CA from |
| 173 | an {@link java.io.InputStream}, uses it to create a {@link java.security.KeyStore}, |
| 174 | which is then used to create and initialize a |
| 175 | {@link javax.net.ssl.TrustManager}. A {@link javax.net.ssl.TrustManager} is what the system |
| 176 | uses to validate certificates from the server |
| 177 | and—by creating one from a {@link java.security.KeyStore} with one or more CAs—those |
| 178 | will be the only CAs trusted by that {@link javax.net.ssl.TrustManager}.</p> |
| 179 | |
| 180 | <p>Given the new {@link javax.net.ssl.TrustManager}, |
| 181 | the example initializes a new {@link javax.net.ssl.SSLContext} which provides |
| 182 | an {@link javax.net.ssl.SSLSocketFactory} you can use to override the default |
| 183 | {@link javax.net.ssl.SSLSocketFactory} from |
| 184 | {@link javax.net.ssl.HttpsURLConnection}. This way the |
| 185 | connection will use your CAs for certificate validation.</p> |
| 186 | |
| 187 | <p>Here is the example in |
| 188 | full using an organizational CA from the University of Washington:</p> |
| 189 | |
| 190 | <pre> |
| 191 | // Load CAs from an InputStream |
| 192 | // (could be from a resource or ByteArrayInputStream or ...) |
| 193 | CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
| 194 | // From https://www.washington.edu/itconnect/security/ca/load-der.crt |
| 195 | InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt")); |
| 196 | Certificate ca; |
| 197 | try { |
| 198 | ca = cf.generateCertificate(caInput); |
| 199 | System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); |
| 200 | } finally { |
| 201 | caInput.close(); |
| 202 | } |
| 203 | |
| 204 | // Create a KeyStore containing our trusted CAs |
| 205 | String keyStoreType = KeyStore.getDefaultType(); |
| 206 | KeyStore keyStore = KeyStore.getInstance(keyStoreType); |
| 207 | keyStore.load(null, null); |
| 208 | keyStore.setCertificateEntry("ca", ca); |
| 209 | |
| 210 | // Create a TrustManager that trusts the CAs in our KeyStore |
| 211 | String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); |
| 212 | TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); |
| 213 | tmf.init(keyStore); |
| 214 | |
| 215 | // Create an SSLContext that uses our TrustManager |
| 216 | SSLContext context = SSLContext.getInstance("TLS"); |
| 217 | context.init(null, tmf.getTrustManagers(), null); |
| 218 | |
| 219 | // Tell the URLConnection to use a SocketFactory from our SSLContext |
| 220 | URL url = new URL("https://certs.cac.washington.edu/CAtest/"); |
| 221 | HttpsURLConnection urlConnection = |
| 222 | (HttpsURLConnection)url.openConnection(); |
| 223 | urlConnection.setSSLSocketFactory(context.getSocketFactory()); |
| 224 | InputStream in = urlConnection.getInputStream(); |
| 225 | copyInputStreamToOutputStream(in, System.out); |
| 226 | </pre> |
| 227 | |
| 228 | <p>With a custom {@link javax.net.ssl.TrustManager} that knows about your CAs, |
| 229 | the system is able to validate |
| 230 | that your server certificate come from a trusted issuer.</p> |
| 231 | |
| 232 | <p class="caution"><strong>Caution:</strong> |
| 233 | Many web sites describe a poor alternative solution which is to install a |
| 234 | {@link javax.net.ssl.TrustManager} that does nothing. If you do this you might as well not |
| 235 | be encrypting your communication, because anyone can attack your users at a public Wi-Fi hotspot |
| 236 | by using <acronym title="Domain Name System">DNS</acronym> tricks to send your users' |
| 237 | traffic through a proxy of their own that pretends to be your server. The attacker can then |
| 238 | record passwords and other personal data. This works because the attacker can generate a |
| 239 | certificate and—without a {@link javax.net.ssl.TrustManager} that actually |
| 240 | validates that the certificate comes from a trusted |
| 241 | source—your app could be talking to anyone. So don't do this, not even temporarily. You can |
| 242 | always make your app trust the issuer of the server's certificate, so just do it.</p> |
| 243 | |
| 244 | |
| 245 | |
| 246 | <h3 id="SelfSigned">Self-signed server certificate</h3> |
| 247 | |
| 248 | <p>The second case of {@link javax.net.ssl.SSLHandshakeException} is |
| 249 | due to a self-signed certificate, which means the server is behaving as its own CA. |
| 250 | This is similar to an unknown certificate authority, so you can use the |
| 251 | same approach from the previous section.</p> |
| 252 | |
David Friedman | 36b692d | 2013-09-27 10:04:48 -0700 | [diff] [blame] | 253 | <p>You can create your own {@link javax.net.ssl.TrustManager}, |
Scott Main | 0015836 | 2013-01-17 16:58:50 -0800 | [diff] [blame] | 254 | this time trusting the server certificate directly. This has all of the |
| 255 | downsides discussed earlier of tying your app directly to a certificate, but can be done |
| 256 | securely. However, you should be careful to make sure your self-signed certificate has a |
| 257 | reasonably strong key. As of 2012, a 2048-bit RSA signature with an exponent of 65537 expiring |
| 258 | yearly is acceptable. When rotating keys, you should check for <a |
| 259 | href="http://csrc.nist.gov/groups/ST/key_mgmt/index.html">recommendations</a> from an |
| 260 | authority (such as <a href="http://www.nist.gov/">NIST</a>) about what is acceptable.</p> |
| 261 | |
| 262 | |
| 263 | |
| 264 | <h3 id="MissingCa">Missing intermediate certificate authority</h3> |
| 265 | |
| 266 | <p>The third case of {@link javax.net.ssl.SSLHandshakeException} |
| 267 | occurs due to a missing intermediate CA. Most public |
| 268 | CAs don't sign server certificates directly. Instead, they use their main CA certificate, |
| 269 | referred to as the root CA, to sign intermediate CAs. They do this so the root CA can be stored |
| 270 | offline to reduce risk of compromise. However, operating systems like Android typically |
| 271 | trust only root CAs directly, which leaves a short gap of trust between the server |
| 272 | certificate—signed by the intermediate CA—and the certificate verifier, |
| 273 | which knows the root CA. To solve |
| 274 | this, the server doesn't send the client only it's certificate during the SSL handshake, but |
| 275 | a chain of certificates from the server CA through any intermediates necessary to reach a |
| 276 | trusted root CA.</p> |
| 277 | |
| 278 | <p>To see what this looks like in practice, here's the <em>mail.google.com</em> certificate |
| 279 | chain as viewed by the <a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a> |
| 280 | {@code s_client} command:</p> |
| 281 | |
| 282 | <pre class="no-pretty-print"> |
| 283 | $ openssl s_client -connect mail.google.com:443 |
| 284 | --- |
| 285 | Certificate chain |
| 286 | 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com |
| 287 | i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA |
| 288 | 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA |
| 289 | i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority |
| 290 | --- |
| 291 | </pre> |
| 292 | |
| 293 | |
| 294 | <p>This shows that the server sends a certificate for <em>mail.google.com</em> |
| 295 | issued by the <em>Thawte SGC</em> CA, which is an intermediate CA, and a second certificate |
| 296 | for the <em>Thawte SGC</em> CA issued by a <em>Verisign</em> CA, which is the primary CA that's |
| 297 | trusted by Android.</p> |
| 298 | |
| 299 | <p>However, it is not uncommon to configure a server to not include the necessary |
| 300 | intermediate CA. For example, here is a server that can cause an error in Android browsers and |
| 301 | exceptions in Android apps:</p> |
| 302 | |
| 303 | <pre class="no-pretty-print"> |
| 304 | $ openssl s_client -connect egov.uscis.gov:443 |
| 305 | --- |
| 306 | Certificate chain |
| 307 | 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 |
| 308 | 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 |
| 309 | --- |
| 310 | </pre> |
| 311 | |
| 312 | <p>What is interesting to note here is that visiting this server in most desktop browsers |
| 313 | does not cause an error like a completely unknown CA or self-signed server certificate would |
| 314 | cause. This is because most desktop browsers cache trusted intermediate CAs over time. Once |
| 315 | a browser has visited and learned about an intermediate CA from one site, it won't |
| 316 | need to have the intermediate CA included in the certificate chain the next time.</p> |
| 317 | |
| 318 | <p>Some sites do this intentionally for secondary web servers used to serve resources. For |
| 319 | example, they might have their main HTML page served by a server with a full certificate |
| 320 | chain, but have servers for resources such as images, CSS, or JavaScript not include the |
| 321 | CA, presumably to save bandwidth. Unfortunately, sometimes these servers might be providing |
| 322 | a web service you are trying to call from your Android app, which is not as forgiving.</p> |
| 323 | |
| 324 | <p>There are two approaches to solve this issue:</p> |
| 325 | <ul> |
| 326 | <li>Configure the server to |
| 327 | include the intermediate CA in the server chain. Most CAs provide documentation on how to do |
| 328 | this for all common web servers. This is the only approach if you need the site to work with |
| 329 | default Android browsers at least through Android 4.2.</li> |
| 330 | <li>Or, treat the |
| 331 | intermediate CA like any other unknown CA, and create a {@link javax.net.ssl.TrustManager} |
| 332 | to trust it directly, as done in the previous two sections.</li> |
| 333 | </ul> |
| 334 | |
| 335 | |
| 336 | <h2 id="CommonHostnameProbs">Common Problems with Hostname Verification</h2> |
| 337 | |
| 338 | <p>As mentioned at the beginning of this article, |
| 339 | there are two key parts to verifying an SSL connection. The first |
| 340 | is to verify the certificate is from a trusted source, which was the focus of the previous |
| 341 | section. The focus of this section is the second part: making sure the server you are |
| 342 | talking to presents the right certificate. When it doesn't, you'll typically see an error |
| 343 | like this:</p> |
| 344 | |
| 345 | <pre class="no-pretty-print"> |
| 346 | java.io.IOException: Hostname 'example.com' was not verified |
| 347 | at libcore.net.http.HttpConnection.verifySecureSocketHostname(HttpConnection.java:223) |
| 348 | at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:446) |
| 349 | at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290) |
| 350 | at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240) |
| 351 | at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282) |
| 352 | at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177) |
| 353 | at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271) |
| 354 | </pre> |
| 355 | |
| 356 | |
| 357 | <p>One reason this can happen is due to a server configuration error. The server is |
| 358 | configured with a certificate that does not have a subject or subject alternative name fields |
| 359 | that match the server you are trying to reach. It is possible to have one certificate be used |
| 360 | with many different servers. For example, looking at the <em>google.com</em> certificate with |
| 361 | <a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a> {@code |
| 362 | s_client -connect google.com:443 | openssl x509 -text} you can see that a subject |
| 363 | that supports <em>*.google.com</em> but also subject alternative names for <em>*.youtube.com</em>, |
| 364 | <em>*.android.com</em>, and others. The error occurs only when the server name you |
| 365 | are connecting to isn't listed by the certificate as acceptable.</p> |
| 366 | |
| 367 | <p>Unfortunately this can happen for another reason as well: <a |
| 368 | href="http://en.wikipedia.org/wiki/Virtual_hosting">virtual hosting</a>. When sharing a |
| 369 | server for more than one hostname with HTTP, the web server can tell from the HTTP/1.1 request |
| 370 | which target hostname the client is looking for. Unfortunately this is complicated with |
| 371 | HTTPS, because the server has to know which certificate to return before it sees the HTTP |
| 372 | request. To address this problem, newer versions of SSL, specifically TLSv.1.0 and later, |
| 373 | support <a href="http://en.wikipedia.org/wiki/Server_Name_Indication">Server Name Indication |
| 374 | (SNI)</a>, which allows the SSL client to specify the intended |
| 375 | hostname to the server so the proper certificate can be returned.</p> |
| 376 | |
| 377 | <p>Fortunately, {@link javax.net.ssl.HttpsURLConnection} supports |
| 378 | SNI since Android 2.3. Unfortunately, Apache |
| 379 | HTTP Client does not, which is one of the many reasons we discourage its use. One workaround |
| 380 | if you need to support Android 2.2 (and older) or Apache HTTP Client is to set up an alternative |
| 381 | virtual host on a unique port so that it's unambiguous which server certificate to return.</p> |
| 382 | |
| 383 | <p>The more drastic alternative is to replace {@link javax.net.ssl.HostnameVerifier} |
| 384 | with one that uses not the |
| 385 | hostname of your virtual host, but the one returned by the server by default.</p> |
| 386 | |
| 387 | <p class="caution"><strong>Caution:</strong> Replacing {@link javax.net.ssl.HostnameVerifier} |
| 388 | can be <strong>very dangerous</strong> if the other virtual host is |
| 389 | not under your control, because a man-in-the-middle attack could direct traffic to another |
| 390 | server without your knowledge.</p> |
| 391 | |
| 392 | <p>If you are still sure you want to override hostname verification, here is an example |
| 393 | that replaces the verifier for a single {@link java.net.URLConnection} |
| 394 | with one that still verifies that the hostname is at least on expected by the app:</p> |
| 395 | |
| 396 | <pre> |
| 397 | // Create an HostnameVerifier that hardwires the expected hostname. |
| 398 | // Note that is different than the URL's hostname: |
| 399 | // example.com versus example.org |
| 400 | HostnameVerifier hostnameVerifier = new HostnameVerifier() { |
| 401 | @Override |
| 402 | public boolean verify(String hostname, SSLSession session) { |
| 403 | HostnameVerifier hv = |
| 404 | HttpsURLConnection.getDefaultHostnameVerifier(); |
| 405 | return hv.verify("example.com", session); |
| 406 | } |
| 407 | }; |
| 408 | |
| 409 | // Tell the URLConnection to use our HostnameVerifier |
| 410 | URL url = new URL("https://example.org/"); |
| 411 | HttpsURLConnection urlConnection = |
| 412 | (HttpsURLConnection)url.openConnection(); |
| 413 | urlConnection.setHostnameVerifier(hostnameVerifier); |
| 414 | InputStream in = urlConnection.getInputStream(); |
| 415 | copyInputStreamToOutputStream(in, System.out); |
| 416 | </pre> |
| 417 | |
| 418 | <p>But remember, if you find yourself replacing hostname verification, especially |
| 419 | due to virtual hosting, it's still <strong>very dangerous</strong> if the other virtual host is |
| 420 | not under your control and you should find an alternative hosting arrangement |
| 421 | that avoids this issue.</p> |
| 422 | |
| 423 | |
| 424 | |
| 425 | |
| 426 | <h2 id="WarningsSslSocket">Warnings About Using SSLSocket Directly</h2> |
| 427 | |
| 428 | <p>So far, the examples have focused on HTTPS using {@link javax.net.ssl.HttpsURLConnection}. |
| 429 | Sometimes apps need to use SSL separate from HTTP. For example, an email app might use SSL variants |
| 430 | of SMTP, POP3, or IMAP. In those cases, the app would want to use {@link javax.net.ssl.SSLSocket} |
| 431 | directly, much the same way that {@link javax.net.ssl.HttpsURLConnection} does internally.</p> |
| 432 | |
| 433 | <p>The techniques described so |
| 434 | far to deal with certificate verification issues also apply to {@link javax.net.ssl.SSLSocket}. |
| 435 | In fact, when using a custom {@link javax.net.ssl.TrustManager}, what is passed to |
| 436 | {@link javax.net.ssl.HttpsURLConnection} is an {@link javax.net.ssl.SSLSocketFactory}. |
| 437 | So if you need to use a custom {@link javax.net.ssl.TrustManager} with an |
| 438 | {@link javax.net.ssl.SSLSocket}, follow |
| 439 | the same steps and use that {@link javax.net.ssl.SSLSocketFactory} to create your |
| 440 | {@link javax.net.ssl.SSLSocket}.</p> |
| 441 | |
| 442 | <p class="caution"><strong>Caution:</strong> |
| 443 | {@link javax.net.ssl.SSLSocket} <strong>does not</strong> perform hostname verification. It is |
| 444 | up the your app to do its own hostname verification, preferably by calling {@link |
| 445 | javax.net.ssl.HttpsURLConnection#getDefaultHostnameVerifier()} with the expected hostname. Further |
| 446 | beware that {@link javax.net.ssl.HostnameVerifier#verify HostnameVerifier.verify()} |
| 447 | doesn't throw an exception on error but instead returns a boolean result that you must |
| 448 | explicitly check.</p> |
| 449 | |
| 450 | <p>Here is an example showing how you can do this. It shows that when connecting to |
| 451 | <em>gmail.com</em> port 443 without SNI support, you'll receive a certificate for |
| 452 | <em>mail.google.com</em>. This is expected in this case, so check to make sure that |
| 453 | the certificate is indeed for <em>mail.google.com</em>:</p> |
| 454 | |
| 455 | <pre> |
| 456 | // Open SSLSocket directly to gmail.com |
| 457 | SocketFactory sf = SSLSocketFactory.getDefault(); |
| 458 | SSLSocket socket = (SSLSocket) sf.createSocket("gmail.com", 443); |
| 459 | HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); |
| 460 | SSLSession s = socket.getSession(); |
| 461 | |
| 462 | // Verify that the certicate hostname is for mail.google.com |
| 463 | // This is due to lack of SNI support in the current SSLSocket. |
| 464 | if (!hv.verify("mail.google.com", s)) { |
| 465 | throw new SSLHandshakeException("Expected mail.google.com, " |
| 466 | "found " + s.getPeerPrincipal()); |
| 467 | } |
| 468 | |
| 469 | // At this point SSLSocket performed certificate verificaiton and |
| 470 | // we have performed hostname verification, so it is safe to proceed. |
| 471 | |
| 472 | // ... use socket ... |
| 473 | socket.close(); |
| 474 | </pre> |
| 475 | |
| 476 | |
| 477 | |
| 478 | <h2 id="Blacklisting">Blacklisting</h2> |
| 479 | |
| 480 | <p>SSL relies heavily on CAs to issue certificates to only the properly verified owners |
| 481 | of servers and domains. In rare cases, CAs are either tricked or, in the case of <a |
| 482 | href="http://en.wikipedia.org/wiki/Comodo_Group#Breach_of_security">Comodo</a> or <a |
| 483 | href="http://en.wikipedia.org/wiki/DigiNotar">DigiNotar</a>, breached, |
| 484 | resulting in the certificates for a hostname to be issued to |
| 485 | someone other than the owner of the server or domain.</p> |
| 486 | |
| 487 | <p>In order to mitigate this risk, Android has the ability to blacklist certain certificates or even |
| 488 | whole CAs. While this list was historically built into the operating system, starting in |
| 489 | Android 4.2 this list can be remotely updated to deal with future compromises.</p> |
| 490 | |
| 491 | |
| 492 | |
| 493 | <h2 id="Pinning">Pinning</h2> |
| 494 | |
| 495 | <p>An app can further protect itself from fraudulently issued certificates by a |
| 496 | technique known as pinning. This is basically using the example provided in the unknown CA case |
| 497 | above to restrict an app's trusted CAs to a small set known to be used by the app's servers. This |
| 498 | prevents the compromise of one of the other 100+ CAs in the system from resulting in a breach of |
| 499 | the apps secure channel.</p> |
| 500 | |
| 501 | |
| 502 | |
| 503 | <h2 id="ClientCert">Client Certificates</h2> |
| 504 | |
| 505 | <p>This article has focused on the user of SSL to secure communications with servers. SSL also |
| 506 | supports the notion of client certificates that allow the server to validate the identity of a |
| 507 | client. While beyond the scope of this article, the techniques involved are similar to specifying |
| 508 | a custom {@link javax.net.ssl.TrustManager}. |
| 509 | See the discussion about creating a custom {@link javax.net.ssl.KeyManager} in the documentation for |
| 510 | {@link javax.net.ssl.HttpsURLConnection}.</p> |
| 511 | |
| 512 | |
| 513 | |
| 514 | |
| 515 | |
| 516 | |
| 517 | |
| 518 | |
| 519 | |
| 520 | |
| 521 | |
| 522 | |
| 523 | |
| 524 | |
| 525 | |
| 526 | |
| 527 | |
| 528 | |
| 529 | |
| 530 | |
| 531 | |
| 532 | |
| 533 | |
| 534 | |
| 535 | |
| 536 | |
| 537 | |
| 538 | |
| 539 | |
| 540 | |
| 541 | |