SSL

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.

Prerequisites

Understanding this chapter requires that you have read the core chapters of this book, particularly the chapter on Internet access.

Basic SSL Operation

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.

Problems in Paradise

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.

Self-Signed Certificate

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.

Wildcard Certificate

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

Custom Certificate Authority

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 Attacks

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.

Disabling SSL Certificate Validation

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.

Ignoring Domain Names

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.

Hacked CAs

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.

Introducing Network Security Configuration

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:

The Native Android 7.0 Version

On 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.

The CWAC-NetSecurity Backport

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);
(from Internet/CA/app/src/main/java/com/commonsware/android/downloader/Downloader.java)

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.

SSL Problems and Network Security Configuration

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.

Pinning the Certificate Authority

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>
(from Internet/CA/app/src/main/res/xml/network_comodo.xml)

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>
(from Internet/CA/app/src/main/AndroidManifest.xml)

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'
}
(from Internet/CA/app/build.gradle)

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.

Unusual Certificate Authorities

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>
(from Internet/CA/app/src/main/res/xml/network_verisign_system.xml)

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.

Pinning the Certificate

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>
(from Internet/CA/app/src/main/res/xml/network_pin.xml)

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.

Self-Signed Certificates

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>
(from Internet/CA/app/src/main/res/xml/network_selfsigned.xml)

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.

Self-Signed Certificates for Debug Builds

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>
(from Internet/CA/app/src/main/res/xml/network_override.xml)

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.

Blocking Cleartext Traffic

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+.

Supporting User-Added Certificates

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.

Other SSL Strengthening Techniques

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.

Certificate Memorizing

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:

  1. The HTTPS server changed certificates for legitimate reasons
  2. An attacker is providing an alternative certificate

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.

Requiring Encryption, Android 6.0 Style

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.

Watching for Encryption

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.

Advanced Uses of CWAC-NetSecurity

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.

Using Alternative Network Security Configuration XML

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.

Using the Backport Directly

You do not have to use TrustManagerBuilder to use the network security configuration backport. If you wish to use it directly:

Integrating with Other HTTP Client Libraries

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.

Adding the TrustManager

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
}

Handling Cleartext

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.

Handling Redirects

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.

Debugging Certificate Chains

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.

NetCipher

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.