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.
This chapter will explore various SSL scenarios and how to address them.
Understanding this chapter requires that you have read the core chapters of this book, particularly the chapter on Internet access.
Generally speaking, SSL “just works”, for ordinary sites with ordinary certificates.
If you use an https:
URL with HttpUrlConnection
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, 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
if your minSdkVersion
is less than 14.
For example, the Retrofit and Picasso sample apps from
the chapter on Internet access both use
https://api.stackexchange.com
for their service endpoint. As a result, those
requests — for the API JSON, at least — will go over SSL. You would need to
log the URLs used for the image avatars to see whether StackExchange gives you https
URLs or not.
Ideally, SSL just works.
In practice, it often does, but depending on your app and your situation, you may encounter issues, such as:
And so on.
Here are some more details about some common SSL problems.
SSL certificates used for public Web sites are usually backed by a “root certificate authority” that is well-known. That is not always the case.
One case is when the certificate is “self-signed”, meaning that it was generated by somebody without involving a certificate authority. If you have shipped a production Android app, you created a self-signed certificate when you created your production key store. And you have been using a system-generated self-signed certificate throughout your development, known as the “debug signing key”.
Self-signed certificates are rarely used on public-facing Web sites, as Web browsers are taught to warn users when such certificates are encountered. However, self-signed certificates might be used on internal servers, particularly test servers and other non-production environments.
There are even some benefits for using a self-signed certificate for production servers, if those servers will be talking only to your own apps and not arbitrary Web browsers.
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
Some larger organizations have set up their own certificate authority. Sometimes, they aspire to become a recognized root certificate authority, but have not been adopted by many browsers. Sometimes, they simply want to have more structure than a pure self-signed certificate but do not necessarily want to have all certificates go through a root certificate authority, perhaps due to expense.
In these cases, Android will reject the SSL certificate, for the same reason it rejects self-signed ones: it cannot validate the certificate chain all the way back to a known root certificate authority. But, with a little work, you can enable Android to support these as well.
Man-in-the-middle (MITM) attacks are a common way of trying to intercept SSL encrypted communications. The “man” in the “middle” might be a proxy server, a different Web site you wind up communicating with via DNS poisoning, etc. The objective of the “man” is to pretend to be the actual Web site or Web service you are trying to communicate with. If your app “falls for it”, your app will open an encrypted channel to the attacker, not your site, and the attacker will have access to the unencrypted data you send over that channel.
Unfortunately, Android apps have a long history of being victims of man-in-the-middle attacks.
“Why Eve and Mallory Love Android: An Analysis of Android SSL (In)Security”,
an analysis of possible man-in-the-middle attacks on Android,
is depressing. One in six surveyed apps explicitly ignored SSL certificate validation
issues, mostly by means of do-nothing TrustManager
implementations as noted
above. Out of a selected 100 apps, 41 could be successfully attacked using
man-in-the-middle techniques, yielding a treasure trove of credit card
information, account credentials for all the major social networks, and so
forth.
Their paper outlines a few ways in which apps can screw up SSL management — the following sections outline some of them.
As mentioned above, if you disable SSL certificate validation, by implementing
and using a do-nothing TrustManager
, you are wide open for man-in-the-middle
attacks. A simple transparent proxy server can pretend to be the real
endpoint — apps ignoring SSL validation entirely will trust that the transparent
proxy is the real endpoint and, therefore, perform SSL key exchange with the
proxy rather than the real site. The proxy, as a result, gets access to everything
the app sends.
A related flaw is when you disable hostname verification. The “common name” (CN) of
the SSL certificate should reflect the domain name being requested. Requesting
https://www.foo.com/something
and receiving an SSL certificate for xkcdhatguy.com
would be indicative of a mis-configured Web server at best and a man-in-the-middle
attack at worst.
By default, this is checked, and if there is no match, you will get errors like:
javax.net.ssl.SSLException: hostname in certificate didn't match: <...>
where the ...
is replaced by whatever domain name you were requesting.
But some developers disable this check. Perhaps during development they were
accessing the server using a private IP address, and they were getting SSLExceptions
when trying to access that server. It is very important to allow Android to
check the hostname for you, which is the default behavior.
The truly scary issue is when the problem stems from the CA itself.
Comodo, TURKTRUST, and other certificate authorities have been hacked, where
nefarious parties gained the ability to create arbitrary certificates backed by
the CA. For example, in
the TURKTRUST case,
Google found that somebody had created a *.google.com
certificate that
had TURKTRUST as the root CA. Any browser — or Android app — that implicitly
trusted TURKTRUST-issued certificates would believe that this certificate was
genuine. This is the ultimate in man-in-the-middle attacks, as code that
is ordinarily fairly well-written will believe the CA and therefore happily
communicate with the attacker.
Even well-intentioned certificate authorities sometimes make mistakes.
StartSSL offered a tool called StartEncrypt
to make it easy to request and install certificates
on a Web server. However, they made mistakes in the Web service API
used by that tool to communicate back to StartSSL’s servers.
Attackers could create SSL certificates for a wide range of existing domains,
including google.com
, facebook.com
, and other widely-used domains.
Those fraudulent certificates could have been used to implement MITM
attacks.
You can use a “network security configuration” to help address those issues. This comes in the form of an XML resource, which you teach Android to use for your network connections. That resource tailors what you do and do not want to accept for SSL connections, such as “yes, I want to accept this self-signed certificate, at least for debug builds of the app” and “yes, I am willing to accept this additional certificate authority”.
This XML resource will have a <network-security-config>
root element.
That in turn will contain:
<base-config>
elements, defining global rules<domain-config>
elements, defining rules to
apply to a specific domain name or set of domain names<debug-overrides>
elements, defining global rules that
will be applied only for debug builds of your appOn Android 7.0 and higher, you can direct Android to apply your network
security configuration by having an android:networkSecurityConfig
attribute on the <application>
element in your manifest:
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/net_security_config">
// other stuff here
</application>
The name of the XML resource does not matter, so long as your
android:networkSecurityConfig
attribute points to it.
On Android 7.0 and higher, your network security configuration will be applied automatically for all network connections, without any Java configuration.
At the time of this writing, Google has not released an official backport of the network security configuration subsystem.
The author of this book converted that subsystem into a library –
CWAC-NetSecurity — that serves as a backport, working back to API Level 17
(Android 4.2). It does not support every feature of the native
implementation, and it requires a bit of Java code to arrange to use
your network security configuration for HTTP requests. However, you can
use the same XML resource structure. As with many backports, the vision
is that you would use the backport until such time as your
minSdkVersion
rises to 24 or higher, at which point you can just use
the native implementation.
The CWAC-NetSecurity library also offers a TrustManagerBuilder
and related classes
to make it easier for developers to integrate the network security
configuration backport, particularly for
OkHttp3
and HttpURLConnection
.
The artifact for this library is distributed via the CWAC repository,
so you will need to configure that in your module’s build.gradle
file,
along with your implementation
statement:
repositories {
maven {
url "https://s3.amazonaws.com/repo.commonsware.com"
}
}
dependencies {
implementation 'com.commonsware.cwac:netsecurity:0.0.1'
implementation 'com.squareup.okhttp3:okhttp:3.4.0'
}
If you are using this library with OkHttp3, you also need to have
a implementation
statement for a compatible OkHttp3 artifact, as shown
above.
If you are using HttpURLConnection
, or tying this code into some
other HTTP client stack, you can skip the OkHttp3 dependency.
Next, add in this <meta-data>
element to your manifest, as a child
of the <application>
element:
<meta-data
android:name="android.security.net.config"
android:resource="@xml/net_security_config" />
The value for android:resource
should be the same XML resource that
you used in the android:networkSecurityConfig
attribute in the
<application>
element for the native network security configuration
support on Android 7.0.
Then, in your code where you want to set up your network communications,
create a TrustManagerBuilder
and teach it to load the configuration
from the manifest:
TrustManagerBuilder tmb=
new TrustManagerBuilder().withManifestConfig(ctxt);
(where ctxt
is some Context
)
If you are using OkHttp3, create your basic OkHttpClient.Builder
,
then call:
OkHttp3Integrator.applyTo(tmb, okb);
(where tmb
is the TrustManagerBuilder
from before, and okb
is your OkHttpClient.Builder
)
At this point, you can create your OkHttpClient
from the Builder
and start using it.
If you are using HttpURLConnection
, you can call applyTo()
on
the TrustManagerBuilder
itself, passing in the HttpURLConnection
.
Afterwards, you can start using the HttpURLConnection
to make your
HTTP request:
URL url=new URL(i.getData().toString());
HttpURLConnection c=
(HttpURLConnection)url.openConnection();
TrustManagerBuilder tmb=
new TrustManagerBuilder().withManifestConfig(this);
tmb.applyTo(c);
In either case, on Android 7.0+ devices, withManifestConfig()
will
not use the backport. Instead, the platform-native implementation
of the network security configuration subsystem will be used. On
Android 4.2-6.0 devices, the backport will be used.
With all that as prologue, let’s examine how the network security configuration subsystem — native or backport — can address some of the SSL issues outlined earlier in this chapter.
The sample code for these scenarios comes from the
Internet/CA
sample application. This application is based on some of the samples
from the chapter on notifications, that use HttpURLConnection
to download a PDF from the CommonsWare site.
Your app may only communicate with one server, such as an employee-only server for your organization. To help limit the risk of possible MITM attacks, you might want to lock down your app, to only work with certificates coming from your chosen certificate authority for this server. That way, in addition to the other logistical problems facing attackers, they would need to get a forged SSL certificate from your certificate provider, instead of a forged SSL certificate from any certificate provider.
To make this work, first, you will need a PEM or DER file representing
the root certificate for the certificate authority. Usually, the
certificate authority will publish one of these on its Web site. You
will need to put that file in res/raw/
of your project, under a suitable
resource name. For this scenario, in the sample app, there are two
raw resources of note: addtrustexternalcaroot.pem
and verisign_class3.pem
,
for Comodo and Verisign, respectively.
Next, you will need to create your network security configuration.
As noted above, this is an XML resource, in res/xml/
, that describes
what changes you wish to make to the mix of supported certificate authorities.
In the sample app, one such resource is res/xml/network_comodo.xml
:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="false" usesCleartextTraffic="false">wares.commonsware.com</domain>
<trust-anchors>
<certificates src="@raw/addtrustexternalcaroot" />
</trust-anchors>
</domain-config>
</network-security-config>
As mentioned previously, the root element is
<network-security-config>
. In there, you can
have one or more <domain-config>
elements, describing the rules that
you wish to apply to certain domains being used by your app.
A <domain-config>
element will have one or more <domain>
elements, listing
domains that this particular configuration controls. Here, we have just one,
for wares.commonsware.com
. The includeSubdomains
attribute indicates whether
this rule applies to subdomains of the base domain, such as foo.wares.commonsware.com
.
A <domain-config>
element can have a <trust-anchors]
element, listing
what certificates to use to validate SSL connections made to this domain.
Those certificates are identified by <certificate>
elements, usually pointing
to raw resources that are the PEM or DER files for those certificate
authorities. In this case, we point to the addtrustexternalcaroot
resource.
To teach Android that you have this network security configuration that
you wish to apply, you will need add an android:networkSecurityConfig
attribute (for the native Android 7.0 code) and perhaps a <meta-data>
element to the
<application>
element of your manifest (for the CWAC-NetSecurity backport):
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/${networkSecurityConfig}">
<activity
android:name="DownloaderDemo"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="Downloader"
android:permission="android.permission.BIND_JOB_SERVICE" />
<provider
android:name="LegacyCompatFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<meta-data
android:name="android.security.net.config"
android:resource="@xml/${networkSecurityConfig}" />
</application>
In this case, the resource value used in both places
is not a simple XML resource name, like
@xml/network_comodo
, though that will be what most apps will use.
This sample application has different product flavors for applying different
network security configurations, configured in build.gradle
. Those
product flavors use manifestPlaceholders
to indicate which XML
resource to apply for that flavor:
apply plugin: 'com.android.application'
def WARES='"https://wares.commonsware.com/excerpt-7p0.pdf"'
def SELFSIGNED='"https://scrap.commonsware.com:3001/excerpt-7p0.pdf"'
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
defaultConfig {
minSdkVersion 17
targetSdkVersion 27
}
flavorDimensions "default"
productFlavors {
comodo {
dimension "default"
resValue "string", "app_name", "CA Validation Demo"
applicationId "com.commonsware.android.downloader.ca.comodo"
manifestPlaceholders=
[networkSecurityConfig: 'network_comodo']
buildConfigField "String", "URL", WARES
}
verisign {
dimension "default"
resValue "string", "app_name", "Invalid CA Validation Demo"
applicationId "com.commonsware.android.downloader.ca.verisign"
manifestPlaceholders=
[networkSecurityConfig: 'network_verisign']
buildConfigField "String", "URL", WARES
}
system {
dimension "default"
resValue "string", "app_name", "System CA Validation Demo"
applicationId "com.commonsware.android.downloader.ca.system"
manifestPlaceholders=
[networkSecurityConfig: 'network_verisign_system']
buildConfigField "String", "URL", WARES
}
pin {
dimension "default"
resValue "string", "app_name", "Cert Pin Demo"
applicationId "com.commonsware.android.downloader.ca.pin"
manifestPlaceholders=
[networkSecurityConfig: 'network_pin']
buildConfigField "String", "URL", WARES
}
invalidPin {
dimension "default"
resValue "string", "app_name", "Cert Pin Demo"
applicationId "com.commonsware.android.downloader.ca.invalidpin"
manifestPlaceholders=
[networkSecurityConfig: 'network_invalid_pin']
buildConfigField "String", "URL", WARES
}
selfSigned {
dimension "default"
resValue "string", "app_name", "Self-Signed Demo"
applicationId "com.commonsware.android.downloader.ca.ss"
manifestPlaceholders=
[networkSecurityConfig: 'network_selfsigned']
buildConfigField "String", "URL", SELFSIGNED
}
override {
dimension "default"
resValue "string", "app_name", "Debug Override Demo"
applicationId "com.commonsware.android.downloader.ca.debug"
manifestPlaceholders=
[networkSecurityConfig: 'network_override']
buildConfigField "String", "URL", SELFSIGNED
}
}
}
repositories {
maven {
url "https://s3.amazonaws.com/repo.commonsware.com"
}
}
dependencies {
implementation 'com.android.support:support-v13:27.0.2'
implementation 'com.commonsware.cwac:provider:0.5.3'
implementation 'com.commonsware.cwac:netsecurity:0.4.4'
}
The CommonsWare Warescription Web site, at the time of this writing, uses an SSL
certificate backed by Comodo. Running the comodoDebug
build variant
should successfully download the PDF file, as the SSL certificate
will be validated properly. However, running the verisignDebug
build variant will fail the SSL validation and crash:
03-22 12:51:01.662 27356-27418/com.commonsware.android.downloader.ca E/Exception downloading file
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:339)
at com.android.okhttp.Connection.connectTls(Connection.java:235)
at com.android.okhttp.Connection.connectSocket(Connection.java:199)
at com.android.okhttp.Connection.connect(Connection.java:172)
at com.android.okhttp.Connection.connectAndSetOwner(Connection.java:367)
at com.android.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:130)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:329)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:246)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:457)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:405)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getHeaders(HttpURLConnectionImpl.java:162)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getHeaderField(HttpURLConnectionImpl.java:206)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getHeaderField(DelegatingHttpsURLConnection.java:190)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getHeaderField(HttpsURLConnectionImpl.java)
at com.commonsware.android.downloader.Downloader.onHandleIntent(Downloader.java:70)
If you have multiple certificate authorities that you wish to support,
you can have multiple <certificate>
elements, or a <certificate>
element pointing to a file with multiple PEM or DER entries.
Perhaps your organization runs its own certificate authority (e.g., for
internal servers). Or perhaps your organization is using a regular
certificate authority, but one that is too new to be recognized
by Android. You could cover the unexpected certificate authority by
using the <certificate>
elements shown above.
But, what happens if you want to support something custom and regular certificate authorities as well?
In that case, there is a special <certificate>
element that you can
add:
<certificates src="system"/>
The value system
, instead of a reference to a raw resource, indicates
that the default system set of certificate authorities should be considered
to be valid.
The systemDebug
build variant uses a different network security configuration:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="false" usesCleartextTraffic="false">wares.commonsware.com</domain>
<trust-anchors>
<certificates src="@raw/verisign_class3" />
<certificates src="system" />
</trust-anchors>
</domain-config>
</network-security-config>
Here, first, we pull in Verisign’s root certificate. If that were all
we had (as you can see in the network_verisign.xml
resource file),
an attempt to download something from wares.commonsware.com
would fail,
as that site uses a Comodo certificate, not a Verisign one. However,
we also have the system
set of certificate authorities. Since Comodo
is a major certificate authority, it is included in Android’s default set,
and so our download should succeed.
Perhaps even supporting any CA’s certificates will be too much of a risk for you and your users. For example, perhaps your site’s certificate is from a certificate authority that has issued fraudulent credentials in the past, and so you fear that your users might still be at risk of a MITM attack.
You can really narrow things down by pinning your app to your specific certificate. Then, only that one certificate will be accepted, not others that might be issued, for your domain, by your certificate authority, either through social engineering, nation-state duress, or whatever.
To do this, you will use a <pin-set>
element, instead of a <certificate>
element, in your network security configuration, as seen in the network_pin
resource:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="false" usesCleartextTraffic="false">wares.commonsware.com</domain>
<pin-set expiration="2020-05-01">
<pin digest="SHA-256">sF1A3ez70l81aUjLwU6KiAMmOyPNFQDueJH+4YDWppo=</pin>
</pin-set>
</domain-config>
</network-security-config>
The <pin-set>
element can include one or more <pin>
elements,
each of which has a digest
attribute and a value. The digest
value
has to be SHA-256
at the present time, though perhaps other hash algorithms
will be supported in the future. The value of the <pin>
element is
the base64-encoded SHA-256 hash of the SubjectPublicKeyInfo
field
of the X509 certificate of the server.
To generate that value, you will need to use a tool like openssl
.
Given a PEM file named server.crt
, you can generate the hash for that
server using the following command:
openssl x509 -in server.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
(NOTE: this should appear all on one line but will be word-wrapped to the size of the book page)
The <pin-set>
element can also have an expiration
attribute, with a date
in yyyy-MM-dd
format. Prior to this date, the SSL certificate of the
server must match one of the pins. On or after this date, the pins are
ignored. For example, you might choose a date that is a bit before the
date when the SSL certificate itself will expire.
This has the benefit of allowing the app to work even if you
fail to update the app and supply a new pin for a new SSL certificate,
or if you do update that app but the user does not install the update
in time. On the other hand, manually altering the device date and time
can bypass your pin.
This behavior — pin expiration allowing formerly-blocked access — is
a bit unusual. Typically, with security, we “fail closed”, meaning that
once something has expired, no access is allowed. Instead, <pin-set>
specifically “fails open”, meaning that once it expires, security is
weakened. In this case, Google elected to focus on utility over security.
As Moxie Marlinspike points out, one way to avoid having your app be the victim of a man-in-the-middle attack due to a hijacked certificate authority is to simply not use a certificate authority.
Certificate authorities are designed for use by general-purpose clients (e.g., Web browsers) hitting general-purpose servers (e.g., Web servers). In the case where you control both the client and the server, you don’t need a certificate authority. You merely need to have a self-signed certificate that both ends know about.
This works well if the Web server is solely functioning as a Web service to deliver data to your Android app, or perhaps other native apps on other platforms for which you can also support self-signed certificates. Depending upon your server’s capabilities, you might be able to arrange to have the same server-side application logic be available both from a self-signed certificate on one domain (for use with apps) and from a CA-rooted certificate for another domain (for use with Web browsers).
However, it is very possible that the staff who manage the servers will reject the notion of using a self-signed certificate, perhaps in an effort to minimize the complexity of supporting multiple SSL paths (for browsers and apps). Or, you may not control the server well enough to go with a self-signed certificate, such as if you are using a cloud computing provider.
However, if self-signed certificates are an option for you, the network security configuration code makes them simple to integrate.
You can use the PEM or DER file from your self-signed certificate
much as you would one from a certificate authority: put in res/raw/
and set up your network security configuration XML to match:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="false" usesCleartextTraffic="false">scrap.commonsware.com</domain>
<trust-anchors>
<certificates src="@raw/example" />
</trust-anchors>
</domain-config>
</network-security-config>
This is from the selfSigned
product flavor. Note that it will not work
on your development machine, as you do not have a Web
server with a self-signed SSL certificate at scrap.commonsware.com
.
However, this shows the basic setup, as being the same as before.
This site
has instructions for setting up a self-signed certificate. The CRT
file that is created (e.g., example.crt
) is what you would put in your
app.
If you are only using a self-signed certificate for debuggable builds
(e.g., debug
build type), you can use the <debug-overrides>
XML
element in your network security configuration. This adds your
self-signed certificates to the roster of trust anchors, but only
for debuggable builds. For non-debuggable builds (e.g., release
build type), your self-signed SSL certificate will be ignored.
You can see this in the network_override.xml
resource:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<debug-overrides>
<trust-anchors>
<certificates src="@raw/example"/>
</trust-anchors>
</debug-overrides>
</network-security-config>
This is for the override
product flavor which, like selfSigned
,
will not work for you, as you will not have a Web server using that
SSL certificate.
For a domain, or perhaps for everything in your app, you might want to ensure that you always are using SSL… even to the point of being willing to crash your app if you are not using SSL. While this is an extreme measure, some apps have those sorts of security requirements.
The network security configuration subsystem supports a
cleartextTrafficPermitted
attribute on <base-config>
and
<domain-config>
:
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
If set to false
, this means that you want to block all “cleartext”
(non-SSL) traffic for the scope of that element.
The native implementation of network security configuration supports
this flag for most Internet communications. Notably, WebView
does
not support it.
The CWAC-NetSecurity backport, if you are using the OkHttp3 integration,
attempts to honor this, by checking the scheme for requests. If you
make an http
request, but you have cleartextTrafficPermitted="false"
for the appropriate scope (e.g., for the domain in the URL), the
request is rejected. However, this is not quite as strong as the
native implementation, and it certainly does not affect anything other
than the OkHttp3 integration.
On Android 6.0, you have
another option of enabling this same sort of check.
You also have a way to have
StrictMode
validate cleartext traffic on
Android 6.0+.
The native Android 7.0+ network security configuration subsystem not
only allows you to use [certificates src="system" /]
to say “we also
allow any standard certificate authorities here”, but also
[certificates src="user" /]
. This indicates that certificate authorities
added by the user, through Settings, should be honored as well.
By default, for apps with targetSdkVersion
set to 24 or higher,
user-added certificates are ignored unless [certificates src="user" /]
is included in a network security configuration.
Such user-added certificate authorities are a bit controversial in Android app development. On the one hand, they allow users to add support for unrecognized authorities, in case Android is slow to adopt them, and without apps having to do anything. On the other hand, those user-added certificate authorities are global in scope, rather than being tied to specific domains.
Note that this feature is not available in the CWAC-NetSecurity
backport. [certificates src="user" /]
is ignored. User-added certificate
authorities are lumped in with the system-defined certificate authorities,
so if you have [certificates src="system" /]
, you will get certificate
authorities from both sources.
Not everything that one can do to improve SSL security is covered by either the native network security configuration implementation or the CWAC-NetSecurity backport. Here are some other possibilities to consider.
If your app needs to connect to arbitrary SSL servers — perhaps ones configured by the user (e.g., email client) or are intrinsic to the app’s usage (e.g., URLs in a Web browser) — detecting man-in-the-middle attacks boils down to proper SSL certificate validation… and praying for no hacked CA certificates.
However, one way to incrementally improve security is to use certificate memorizing. With this technique, each time you see a certificate that you have not seen before, or perhaps a different certificate for a site visited previously, you ask the user to confirm that it is OK to proceed.
The idea here is that even if we cannot tell, absolutely, whether a given certificate is genuine or from an attacker, we can detect differences in certificates over time. So, if the user has been seeing certificate A, and now all of a sudden receives certificate B instead, there are two main possibilities:
So, what we do is check certificates against a roster that the user has approved before. If the newly-received certificate is not in that roster, we fail the HTTPS request, but raise a custom exception so that your code can detect this case and ask the user for approval to proceed.
Technically savvy users may be able to deduce whether the certificate is indeed genuine; slightly less-savvy users might simply contact the site to see if this is expected behavior. The downside is that technically unsophisticated users might be baffled by the question of whether or not they should accept the certificate and may take their confusion out on you, the developer of the app that is asking the question.
There is
a standalone implementation of a MemorizingTrustManager
that you could consider using. It has been around for a few years, with a slow-but-steady
set of updates.
However, that library handles asking the user for acceptance of the certificates for you, rather than raising some event that your app can handle itself. In order to tailor the UI, you would need to modify the library itself.
Moreover, the library attempts to handle this UI while your SSL request is in process,
by blocking the background thread upon which you are making the HTTPS request.
A side-effect of this is that MemorizingTrustManager
has some fairly unpleasant
code for trying to block this thread while interacting with the user on the main
application thread. And, if the user takes too long, your request to the server may
time out anyway.
Android 6.0 supports an usesCleartextTraffic
attribute on the
<application>
element in the manifest. This works like the
cleartextTrafficPermitted
option in the network security configuration
subsystem. If this is set to false
,
you are saying that your app not only should be using SSL for everything,
but that you expressly want to crash the app in case you wind up
not using SSL.
If you try to perform plain HTTP requests on Android 6.0 with
usesCleartextTraffic
set to false
,
you will crash when you attempt to download the
file, with a stack trace akin to:
06-19 08:03:46.325 6420-6478/com.commonsware.android.downloader E/com.commonsware.android.downloader.Downloader: Exception in download
java.net.UnknownServiceException: CLEARTEXT communication not supported: []
at com.android.okhttp.Connection.connect(Connection.java:149)
at com.android.okhttp.Connection.connectAndSetOwner(Connection.java:185)
.
.
.
What is really going on “under the covers” is that this attribute
sets a flag that HTTP client APIs can check, electing to fail a request
if the flag says that SSL is required and the request’s URL does not
have the https
scheme. Android’s built-in HTTP clients should support
this flag, but third-party HTTP stacks that manage their own socket
connections may not. Also note that WebView
does not honor
usesCleartextTraffic
.
The downside of usesCleartextTraffic
is that it is “all or nothing” and
always terminates your process. The same thing holds true for using
cleartextTrafficPermitted
with the network security configuration
in the <base-config>
element. That is wonderful in situations where
SSL is crucial. It is less wonderful if your app crashes in production
in situations where SSL would be a really good idea but is unavailable
for whatever reason.
StrictMode
on API Level 23+ devices supports a way to be warned if your app performs
unencrypted network operations, via a detectCleartextNetwork()
method on StrictMode.VmPolicy.Builder
. You can configure this, and
suitable penalties, alongside the rest of your StrictMode
setup. This
can include doing different things for debug
versus release
builds,
for example. So, in a debug
build, you might choose penaltyDeath()
to crash the process, while in a release
build, you settle for penaltyLog()
or something else less drastic.
If you are using a build server, you could set it up to watch
for StrictMode
Logcat messages coming from your test suite to find out about
these accesses.
Adding a couple of lines of Java code, along with the dependency, is all that you need to use CWAC-NetSecurity to gain the benefits of the backport of the network security configuration subsystem. However, CWAC-NetSecurity offers a few more features that may be of use to you.
withManifestConfig()
on TrustManagerBuilder
uses the resource
that you declare in your manifest as the network security configuration
to apply. However, that is fairly inflexible, as you can only define
this in the manifest once. Also, withManifestConfig()
performs the
version check to only apply the backport on pre-7.0 devices.
You can also use withConfig()
, where you provide a Context
and the resource ID
of the XML resource to use for the network security configuration.
This is useful for cases where:
For example, the test suites use withConfig()
, as otherwise we would
need dozens of separate manifests.
You do not have to use TrustManagerBuilder
to use the network security
configuration backport. If you wish to use it directly:
ApplicationConfig
, passing in a ConfigSource
implementation that indicates where the configuration should be pulled
from. Two likely ConfigSource
implementations are ManifestConfigSource
(to use the one defined in the manifest) and XmlConfigSource
(to
use one defined in an arbitrary XML resource).getTrustManager()
on the ApplicationConfig
to get a TrustManager
that will implement the requested configuration.TrustManager
to your HTTP client via whatever API that
client offers for such things. In many cases, that will be by configuring
an SSLContext
to use the TrustManager
, then using the SSLContext
(or an SSLSocketFactory
created by the SSLContext
) with your
HTTP client.If you want to integrate TrustManagerBuilder
and the network security
configuration backport with some other HTTP client API, start by reviewing
the OkHttp3Integrator
class in the netsecurity-okhttp3
library.
This will give you an idea of what is required and how easy it will
be to replicate this class for your particular HTTP client API.
Calling build()
on the TrustManagerBuilder
gives you a
CompositeTrustManager
, set up to implement your desired network
security configuration. You will need to add that to your HTTP client
by one means or another. If size()
on the CompositeTrustManager
returns 0, though, you can skip it, as it means that there are no rules
to be applied (e.g., you used withManifestConfig()
, and your app
is running on an Android 7.0+ device).
So, you might have code that looks like this, where tmb
is a
configured TrustManagerBuilder
:
CompositeTrustManager trustManager=tmb.build();
if (trustManager.size()>0) {
SSLContext ssl=SSLContext.getInstance("TLS");
X509Interceptor interceptor=new X509Interceptor(trustManager, tmb);
ssl.init(null, new TrustManager[]{trustManager}, null);
// apply the SSLContext or ssl.getSocketFactory() to your HTTP client
}
You can call isCleartextTrafficPermitted()
on the CompositeTrustManager
to determine if cleartext traffic should be supported. This takes the
domain name of the Web server you are going to be communicating with
and returns a simple boolean
. If isCleartextTrafficPermitted()
returns false
, you will need to examine the scheme of the URL and
accept or reject the HTTP operation accordingly.
If you fail to do this, then cleartext traffic will be allowed in all
cases, akin to the stock HttpURLConnection
integration.
If your HTTP client automatically traverses server-side redirects
(making the HTTP request for the redirected-to URL), you will need
to handle the cleartext check and the setHost()
call on every
step of the redirection, not just your initial request. In the
case of OkHttp3, this is accomplished via their interceptor framework.
You can call withCertChainListener()
on TrustManagerBuilder
,
providing an implementation of CertChainListener
. Your listener
will be called with onChain()
each time a certificate chain is
encountered. In onChain()
, you can inspect the certificates, dump
their contents to Logcat, or whatever you wish to do.
This is designed for use in development. For example, when writing
the demo/
app, the author used a CertChainListener
to log what
HTTP requests were being made, what domains those were for, and what
root certificates are being used. This in turn led to creating the
network security configuration that matched.
However, logging certificate chains on a production device may result
in security issues. Please only use CertChainListener
in debug
builds.
The Guardian Project has released an Android library project called NetCipher — formerly known as OnionKit — designed to help boost Internet security for Android applications.
In particular, NetCipher helps your application integrate with Orbot, a Tor proxy. Tor (“The Onion Router”) is designed to help with anonymity, having your Internet requests go through a series of Tor routers before actually connecting to your targeted server through some Tor endpoint. Tor is used for everything from mitigating Web site tracking to helping dissidents bypass national firewalls. NetCipher helps your app:
There is a dedicated chapter on NetCipher, if you have interest in this technology.