SSL on Android: The Basics
(In thanks to The Guardian Project for some assistance in assembling this material, I am blogging a portion of The Busy Coder’s Guide to Android Development Version 4.6 that covers SSL on Android. This is the first part of a four-part series. The other parts include:
Note that if you are reading this in late 2013 or beyond, the material in the book may be newer than these blog posts.)
The traditional approach to securing HTTP operations is by means of SSL. Android supports SSL, much as ordinary Java does. Most of the time, you can just allow Android to do its thing with respect to SSL, and you will be fine. However, there may be times when you have to play a more direct role in SSL communications, to handle arbitrary SSL-encrypted endpoints, or to help ensure that your app is not the victim of a man-in-the-middle attack.
Basic SSL Operation
Generally speaking, SSL “just works”, for ordinary sites with ordinary certificates.
If you use an https:
URL with HttpUrlConnection
, HttpClient
, or WebView
,
SSL handshaking will happen automatically, and assuming the certificates check out
OK, you will get your result, just as if you had requested an http:
URL.
However, DownloadManager
only recently added support for SSL. Originally, requesting
a download via DownloadManager
with an https:
scheme would result in
java.lang.IllegalArgumentException: Can only download HTTP URIs
. As of Android 4.0,
SSL is supported. Hence, you need to be careful about making SSL requests via
DownloadManager
to ensure that you are only doing that on a relatively recent version
of Android.
Certificate Verification
The first challenge comes in verifying the SSL certificate.
You can roughly divide SSL certificates into three types:
-
Those issued by a certificate authority (CA) that is recognized by Android (e.g., VeriSign) or was issued by a downstream CA whose upstream CA is one recognized by Android
-
Those issued by a CA that is not recognized by Android
-
Self-signed certificates, whether used temporarily (e.g., during development) or in production
Android can only transparently handle the first set, where the root CA for the certificate
is one recognized by Android. And, for better and for worse, the roster of CAs recognized
by Android varies between OS versions, as Google updates the OS cacerts
roster.
If you encounter an SSL certificate that cannot be verified by Android, you will get
a javax.net.ssl.SSLException: Not trusted server certificate
exception from
HttpUrlConnection
and HttpClient
, and you will need to decide for yourself how
to handle that.
Custom TrustManager
The right solution is to build your own TrustManager
that implements your business
policies.
For example, if you want to validate a self-signed SSL certificate, you can implement
a TrustManager
that does so, by having a custom TrustStore
. A TrustStore
is a set
of certificates (from a CA or self-signed) that a TrustManager
can validate against.
Nikolay Elenkov has
an excellent writeup and sample code
of implementing such a TrustStore
. He also demonstrates how to have a composite
TrustManager
, one that uses the system’s TrustManager
and your own (e.g., configured
with your custom TrustStore
), so certificates that are validated by either
TrustManager
are considered to be valid.
If you are trying to use this technique to validate certificates from a CA that is not recognized by Android, you may need to use Mr. Elenkov’s technique with multiple certificates, representing the upstream chain to the root CA.
Wildcard Certificates
Some certificates are difficult to validate, because they use wildcards.
For example, Amazon S3 is a file storage and serving “cloud” solution from Amazon.com. They
allow you to define “buckets” containing “objects”, where each object then has its own
URL. That URL is based on the name of the bucket and the name of the object. One option is
for you to have the domain name of the URL be based on the name of the bucket, leaving the
path to be solely the name of the object. This works, even with SSL, but Amazon needed to
use a “wildcard SSL certificate”, one that matches *.s3.amazonaws.com
, not just a single
domain name. By default, this will fail on Android, as Android’s stock TrustManager
will not validate wildcards for multiple domain name segments
(e.g., http://misc.commonsware.com.s3.amazonaws.com/foo.txt
). You will get an exception
akin to:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException:
No subject alternative DNS name matching misc.commonsware.com.s3.amazonaws.com found
However, you could write a WildcardTrustManager
or some such that relies on the system
TrustManager
to validate the rest of the certificate, while you validate the domain name
matches the expectated value. The OpenDJ project has
a series of available TrustManager
implementations,
including one that supports wildcards.
Anti-Pattern: Disabling SSL Certificate Validation
You will find various blog posts, StackOverflow answers, and the like that suggest
that you simply disable SSL certificate validation, by implementing an “accept-all”
TrustManager
. Such a TrustManager
basically implements the interface with empty
stubs for methods like checkServerTrusted()
, not throwing any exceptions.
Technically, this works. And, if you are using this only early on in development
and if you swear upon a stack of $RELIGIOUS_TEXTS
that you will replace this
hack by the time you go to production, it is difficult to complain about this
technique.
However, in production, ignoring SSL certificate validation errors opens your app up to man-in-the-middle attacks… which we will examine tomorrow.