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
SSL handshaking will happen automatically, and assuming the certificates check out
OK, you will get your result, just as if you had requested an
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
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
If you encounter an SSL certificate that cannot be verified by Android, you will get
javax.net.ssl.SSLException: Not trusted server certificate exception from
HttpClient, and you will need to decide for yourself how
to handle that.
The right solution is to build your own
TrustManager that implements your business
For example, if you want to validate a self-signed SSL certificate, you can implement
TrustManager that does so, by having a custom
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.
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
will not validate wildcards for multiple domain name segments
http://misc.commonsware.com.s3.amazonaws.com/foo.txt). You will get an exception
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
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
However, in production, ignoring SSL certificate validation errors opens your app up to man-in-the-middle attacks… which we will examine tomorrow.