Security with network protocols

Stay organized with collections Save and categorize content based on your preferences.

The Secure Sockets Layer (SSL)—now technically known as Transport Layer Security (TLS)—is a common building block for encrypted communications between clients and servers. Using TLS incorrectly might let malicious entities intercept an app's data over the network. To help you ensure that this doesn't happen to your app, this article highlights the common pitfalls when using secure network protocols and addresses some larger concerns about using Public-Key Infrastructure (PKI).

We recommend that you also read Android Security Overview as well as Permissions Overview.

Concepts

In a typical TLS usage scenario, a server is configured with a certificate containing a public key and a matching private key. As part of the handshake between a TLS client and server, the server proves it has the private key by signing its certificate with public-key cryptography.

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 isn't in the set, the server is not to be trusted.

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, the client app then has to be updated due to what is essentially a server configuration change. This is especially problematic if the server isn't 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.

To address these downsides, servers are typically configured with certificates from well-known issuers called Certificate Authorities (CAs). The host platform generally contains a list of well-known CAs that it trusts. As of Android 8.0 (API level 26), Android contained over 100 CAs that are updated in each release and don't change from device to device. Similar to a server, a CA has a certificate and a private key. When issuing a certificate for a server, the CA signs 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.

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 gmail.com, or a wildcarded set of hosts, such as *.google.com.

In the snippet from a command line that follows, the openssl tool's s_client command looks at Wikipedia's server certificate information. It specifies port 443 because that is the default for HTTPS. The command sends the output of openssl s_client to openssl x509, which formats information about certificates according to the X.509 standard. Specifically, the command asks for the subject, which contains the server name information, and the issuer, which identifies the CA.

$ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
subject= /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/CN=*.wikipedia.org
issuer= /C=US/O=GeoTrust, Inc./CN=RapidSSL CA

You can see that the certificate was issued for servers matching *.wikipedia.org by the RapidSSL CA.

An HTTPS example

Assuming you have a web server with a certificate issued by a well-known CA, you can make a secure request as shown in the following code:

Kotlin

val url = URL("https://wikipedia.org")
val urlConnection: URLConnection = url.openConnection()
val inputStream: InputStream = urlConnection.getInputStream()
copyInputStreamToOutputStream(inputStream, System.out)

Java

URL url = new URL("https://wikipedia.org");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

If you want to tailor the HTTP request, you can cast to an HttpURLConnection. The Android documentation for HttpURLConnection has examples showing how to deal with request and response headers, posting content, managing cookies, using proxies, caching responses, and more. Through these APIs, the Android framework takes care of the details of verifying certificates and hostnames for you.

We recommend using these APIs if at all possible. For situations that require other solutions, the section that follows has information on common problems that can arise.

Common problems verifying server certificates

Suppose that instead of returning content, getInputStream(), throws an exception:

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)

This can happen for several reasons, including:

  1. The CA that issued the server certificate was unknown.
  2. The server certificate wasn't signed by a CA, but was self signed.
  3. The server configuration is missing an intermediate CA.

The following sections discuss how to address these problems while keeping your connection to the server secure.

Unknown certificate authority

In this case, the SSLHandshakeException occurs because you have a CA that isn't trusted by the system. This might be because you have a certificate from a new CA that isn't yet trusted by Android or because 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 rather a private one issued by an organization such as a government, corporation, or education institution for its own use.

You can set your application up to trust custom CAs by configuring your application's Network Security Config, without needing to modify the code inside your application.

Caution: Many web sites describe a poor alternative solution, which is to install a TrustManager that does nothing. Doing this leaves your users vulnerable to attacks when using a public Wi-Fi hotspot, because an attacker can use DNS tricks to send your users' traffic through a proxy 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 TrustManager validating that the certificate comes from a trusted source, you can't block this kind of attack. So don't do this, even temporarily. Instead, make your app trust the issuer of the server's certificate.

Self-signed server certificate

The second case of 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 a similar approach to address it: configure your application's Network Security Config, in this case to have your application trust your own self-signed certificates.

Missing intermediate certificate authority

The third case of 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.

CAs do this so the root CA can be stored offline, to reduce the 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 its certificate alone to the client during the TLS handshake—it sends a chain of certificates from the server CA through any intermediates necessary to reach a trusted root CA.

For example, here's the mail.google.com certificate chain as viewed by the openssl s_client command:

$ openssl s_client -connect mail.google.com:443
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/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
---

This shows that the server sends a certificate for mail.google.com issued by the Thawte SGC CA, which is an intermediate CA, and a second certificate for the Thawte SGC CA issued by a Verisign CA, which is the primary CA that's trusted by Android.

However, a server might not be configured to include the necessary intermediate CA. For example, here is a server that can cause an error in Android browsers and exceptions in Android apps:

$ 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
---

Visiting this server in most desktop browsers doesn't cause an error, unlike a completely unknown CA or self-signed server certificate. 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.

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 that don't 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 isn't as forgiving.

To fix this issue, configure the server to include the intermediate CA in the server chain. Most CAs provide documentation on how to do this for common web servers.

Warnings about using SSLSocket directly

So far, the examples have focused on HTTPS using HttpsURLConnection. Sometimes apps need to use TLS separate from HTTPS. For example, an email app might use TLS variants of SMTP, POP3, or IMAP. In those cases, the app can use SSLSocket directly, much the same way that HttpsURLConnection does internally.

The techniques described so far to deal with certificate verification issues also apply to SSLSocket. In fact, when using a custom TrustManager, what is passed to HttpsURLConnection is a SSLSocketFactory. So if you need to use a custom TrustManager with an SSLSocket, follow the same steps and use that SSLSocketFactory to create your SSLSocket.

Caution: SSLSocket does not perform hostname verification. It is up to your app to do its own hostname verification, preferably by calling getDefaultHostnameVerifier() with the expected hostname. Also, be aware that HostnameVerifier.verify() doesn't throw an exception on error. Instead, it returns a boolean result that you must explicitly check.

Blocked CAs

TLS relies on CAs to issue certificates to only the verified owners of servers and domains. In rare cases, CAs are either tricked or, in the case of Comodo or DigiNotar, breached, resulting in the certificates for a hostname being issued to someone other than the owner of the server or domain.

To mitigate this risk, Android has the ability to add certain certificates or even whole CAs to a denylist. 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.

Restricting your app to specific certificates

Caution: Certificate pinning, the practice of restricting the certificates that are considered valid for your app to those you have previously authorized, is not recommended for Android apps. Future server configuration changes, such as changing to another CA, render apps with pinned certificates unable to connect to the server without receiving a client software update.

If you want to restrict your app to accept only certificates that you specify, it's critical to include multiple backup pins, including at least one key that's fully in your control, and a sufficiently short expiration period to prevent compatibility issues. The Network Security Config provides pinning with these capabilities.

Client certificates

This article focuses on the use of TLS to secure communications with servers. TLS also supports the notion of client certificates that let the server validate the identity of a client. While beyond the scope of this article, the techniques involved are similar to specifying a custom TrustManager.

Nogotofail: A network traffic security testing tool

Nogotofail is a tool gives you an easy way to confirm that your apps are safe against known TLS/SSL vulnerabilities and misconfigurations. It's an automated, powerful, and scalable tool for testing network security issues on any device whose network traffic can be made to go through it.

Nogotofail is useful for three main use cases:

  • Finding bugs and vulnerabilities.
  • Verifying fixes and watching for regressions.
  • Understanding what applications and devices are generating what traffic.

Nogotofail works for Android, iOS, Linux, Windows, ChromeOS, macOS, and in fact any device you use to connect to the internet. A client is available for configuring the settings and getting notifications on Android and Linux, and the attack engine itself can be deployed as a router, VPN server, or proxy.

You can access the tool at the Nogotofail open source project.

Updates to SSL and TLS

Android 10

Some browsers, such as Google Chrome, allow users to choose a certificate when a TLS server sends a certificate request message as part of a TLS handshake. As of Android 10, KeyChain objects honor the issuers and key specification parameters when calling KeyChain.choosePrivateKeyAlias() to show users a certificate selection prompt. In particular, this prompt doesn't contain choices that don't adhere to server specifications.

If there are no user-selectable certificates available, as is the case when no certificates match the server specification or a device doesn't have any certificates installed, the certificate selection prompt doesn't appear at all.

In addition, it isn't necessary on Android 10 or higher to have a device screen lock to import keys or CA certificates into a KeyChain object.

TLS 1.3 enabled by default

In Android 10 and higher, TLS 1.3 is enabled by default for all TLS connections. Here are a few important details about our TLS 1.3 implementation:

  • The TLS 1.3 cipher suites cannot be customized. The supported TLS 1.3 cipher suites are always enabled when TLS 1.3 is enabled. Any attempt to disable them by calling setEnabledCipherSuites() is ignored.
  • When TLS 1.3 is negotiated, HandshakeCompletedListener objects are called before sessions are added to the session cache. (In TLS 1.2 and other previous versions, these objects are called after sessions are added to the session cache.)
  • In some situations where SSLEngine instances throw an SSLHandshakeException on previous versions of Android, these instances throw an SSLProtocolException instead on Android 10 and higher.
  • 0-RTT mode isn't supported.

If desired, you can obtain an SSLContext that has TLS 1.3 disabled by calling SSLContext.getInstance("TLSv1.2"). You can also enable or disable protocol versions on a per-connection basis by calling setEnabledProtocols() on an appropriate object.

Certificates signed with SHA-1 aren't trusted in TLS

In Android 10, certificates that use the SHA-1 hash algorithm aren't trusted in TLS connections. Root CAs haven't issued such certificates since 2016, and they are no longer trusted in Chrome or other major browsers.

Any attempt to connect fails if the connection is to a site that presents a certificate using SHA-1.

KeyChain behavior changes and improvements

Some browsers, such as Google Chrome, allow users to choose a certificate when a TLS server sends a certificate request message as part of a TLS handshake. As of Android 10, KeyChain objects honor the issuers and key specification parameters when calling KeyChain.choosePrivateKeyAlias() to show users a certificate selection prompt. In particular, this prompt doesn't contain choices that don't adhere to server specifications.

If there are no user-selectable certificates available, as is the case when no certificates match the server specification or a device doesn't have any certificates installed, the certificate selection prompt doesn't appear at all.

In addition, it isn't necessary on Android 10 or higher to have a device screen lock to import keys or CA certificates into a KeyChain object.

Other TLS and cryptography changes

There have been several minor changes in the TLS and cryptography libraries that take effect on Android 10:

  • The AES/GCM/NoPadding and ChaCha20/Poly1305/NoPadding ciphers return more accurate buffer sizes from getOutputSize().
  • The TLS_FALLBACK_SCSV cipher suite is omitted from connection attempts with a max protocol of TLS 1.2 or above. Because of improvements in TLS server implementations, we don't recommend attempting TLS-external fallback. Instead, we recommend relying on TLS version negotiation.
  • ChaCha20-Poly1305 is an alias for ChaCha20/Poly1305/NoPadding.
  • Hostnames with trailing dots aren't considered valid SNI hostnames.
  • The supported_signature_algorithms extension in CertificateRequest is respected when choosing a signing key for certificate responses.
  • Opaque signing keys, such as those from Android Keystore, can be used with RSA-PSS signatures in TLS.

HTTPS connection changes

If an app running Android 10 passes null into setSSLSocketFactory(), an IllegalArgumentException occurs. In previous versions, passing null into setSSLSocketFactory() had the same effect as passing in the current default factory.

Android 11

SSL sockets use Conscrypt SSL engine by default

Android's default SSLSocket implementation is based on Conscrypt. Since Android 11, that implementation is internally built on top of Conscrypt's SSLEngine.