The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Securing GraphQL

Any time we deal with clients and servers, security should have paramount importance. GraphQL does not cause security issues to magically vanish, any more than using REST does. So, as we develop our Android GraphQL clients, we need to consider what we need to do to make sure that our users are protected, with particular emphasis on what we can do on the client side to help with that protection.

Securing the Network

Our principal concern with GraphQL is ensuring that the communications meet the “CIA” objectives:

By and large, this involves SSL/TLS, the same as with any other Web service. While there is nothing specific to GraphQL about using HTTPS, it is important to review the steps, as (unfortunately) not all of these are ingrained in every developer’s thought process while programming.

This chapter provides a “quick and dirty” explanation of various techniques. See The Busy Coder’s Guide to Android Development for in-depth coverage of these topics.

SSL Basics

Hopefully, the GraphQL server that you are talking to has set up an SSL certificate and is using that to encrypt the communications between your client and the server. Courtesy of services like Let’s Encrypt, SSL certificates can be free of charge, and so certificate cost should not be a concern for the server team.

That will allow you to use an https URL with your GraphQL client, whether that is using OkHttp directly, using Apollo-Android, or perhaps using something else.

However, we still have some security issues:

OkHttp Certificate Pinning

One way to address MITM attacks is through certificate pinning. You include in your app details about the specific SSL certificate that you expect to see when connecting to a particular server. An attacker will not have that certificate — instead, they will use a fraudulent one. When your app connects to the attacker’s proxy, it can determine that the attacker is using an unexpected certificate and refuse to connect.

OkHttp offers an implementation of certificate pinning that you can use. The Trips/CW/StaticOkPin sample project demonstrates its use.

Establishing a Pin

When you create your OkHttpClient instance, use an OkHttpClient.Builder. Then, on the Builder, you can call certificatePinner(), supplying a CertificatePinner containing your pinning rules. You create a CertificatePinner via a CertificatePinner.Builder, where you can add() particular pins:

  private OkHttpClient ok=new OkHttpClient.Builder()
    .certificatePinner(new CertificatePinner.Builder()
            .add("graphql-demo.commonsware.com", "sha256/3Zeb2W9lSpG9DrsLH03DRCxcu0j7BFyLVXcR7cZW9tQ=")
            .build())
    .build();

The add() method takes two parameters:

While we refer to this as “certificate pinning”, in truth, this is “public key pinning”, as the validation value (the “pin”) is a hash of the public key information from the certificate itself.

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 one one line but will be word-wrapped to the size of the book page)

And, since this is generating a SHA-256 hash, the actual OkHttp pin will be sha256/, followed by the value generated by the aforementioned openssl snippet.

Given that you have an OkHttpClient configured with the pin, you can then use it directly, via Apollo-Android, etc. Testing to confirm that the pinning is working is merely a matter of changing the pin value (e.g., in the sample app, change the leading 3 to a 4) and confirming that you can no longer connect to the GraphQL server due to a pin mismatch.

Implementing a Pin Set

However, bear in mind that when you use pins, when the server changes SSL certificates, the app needs to change to the new pins.

If you make multiple add() calls on the CertificatePinner.Builder for the same hostname, they represent a logical OR. If the certificate for a given connection matches either of those pins, the connection is accepted.

Hence, several months before your existing SSL certificate expires, you can obtain a new one, then add a pin for it to the Java code. Have the server switch to the new certificate before the old one expires. Everybody who updated their app after you published the update with the second pin will have no problems, as their app will work with both the old and the new certificates.

Pinning Against a Root CA

The problem with having two pins is that not everyone will update to the newer app version in time. You might consider this to be a feature, not a bug, and use the specific exception to tell the user that they need to update their app before they continue using it. In other cases, preventing the user from using the app, just because the SSL certificate changed, might be considered inappropriate.

Another approach is to pin not against your server’s specific SSL certificate, but instead pin against the the root SSL certificate, from the certificate authority (CA) that you obtained your SSL certificate from. So long as any replacement certificates come from the same CA, your pins do not need to change, and so apps depending on those pins continue to work. However:

The simplest way to find the pin values for a root CA is to intentionally crash. Use an invalid pin in your OkHttpClient.Builder configuration, such as sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=. Then, when you crash with the connection failure, look in LogCat for a message from OkHttp akin to the following:

 Peer certificate chain:
   sha256/3Zeb2W9lSpG9DrsLH03DRCxcu0j7BFyLVXcR7cZW9tQ=: CN=graphql-demo.commonsware.com,OU=PositiveSSL,OU=Domain Control Validated
   sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Domain Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
   sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB

The first pin value in this chain will be the one from your specific server and should match what you generated via openssl. The other pin values represent the rest of the SSL certificate chain, from your server to the root CA. Using the last pin value will pin you to the root CA, instead of for your server.

OkHttp Cleartext Ban

Strictly speaking, OkHttp does not have a means of blocking cleartext traffic. However, Jesse Wilson demonstrates the equivalent solution, which is to limit the types of connections that an OkHttpClient will accept to those that imply encryption:

private OkHttpClient ok=new OkHttpClient.Builder()  
    .connectionSpecs(Arrays.asList(
        ConnectionSpec.MODERN_TLS,
        ConnectionSpec.COMPATIBLE_TLS))
    .build();

Network Security Configuration

Android 7.0 added a “network security configuration” subsystem. This allows you to define HTTPS requirements, such as certificate pins, in an XML resource. These are then applied automatically to all Java-based socket communications. As of Android 8.0, the cleartext traffic ban requested via a network security configuration also affects WebView. Hence, if you want to set up common rules that might transcend the use of a single OkHttpClient, network security configuration is one way to go about it.

The Trips/CW/StaticSecure sample project contains a res/xml/network_security.xml configuration file that bans cleartext traffic to graphql-demo.commonsware.com and sets up a certificate pin, akin to what we did using OkHttpClient.Builder:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <domain-config cleartextTrafficPermitted="false">
    <domain includeSubdomains="false">graphql-demo.commonsware.com</domain>
    <pin-set expiration="2020-01-01">
      <pin digest="SHA-256">3Zeb2W9lSpG9DrsLH03DRCxcu0j7BFyLVXcR7cZW9tQ=</pin>
    </pin-set>
  </domain-config>
</network-security-config>

The <domain> element inside the <domain-config> element indicates that the <domain-config> is defining rules for accessing a specific domain (graphql-demo.commonsware.com in this case). The cleartextTrafficPermitted="false" attribute bans cleartext traffic to this domain, and the <pin> inside of the <pin-set> uses the same SHA-256 hash as we used with OkHttpClient.Builder.

The <pin-set> also contains an expiration attribute. When this date arrives, the pins in this set will be ignored, and traffic using certificates that does not match the pin will still be allowed. This weakens security, as MITM attacks will be possible as of the indicated date. However, by “failing open”, it allows the app to continue working, even though the server might have switched to a new certificate. Skipping expiration will result in pins that work indefinitely.

This particular pin pins the server’s own certificate. As with OkHttp, network security configuration will accept a connection if any certificate in its chain matches any of the pins. So, you can use a pin for the root CA’s certificate, if you wish.

To teach Android 7.0+ about these rules, add an android:networkSecurityConfig attribute to the <application> element in the manifest:

  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:networkSecurityConfig="@xml/network_security"
    android:theme="@style/Theme.Apptheme">

No additional configuration is required.

CWAC-NetSecurity

The problem with network security configuration is that it is only available on Android 7.0+. If you like the feature set of network security configuration but need to support older devices, the author of this book maintains CWAC-NetSecurity, a backport of network security configuration that works on API Level 17+ (Android 4.2+). CWAC-NetSecurity also offers additional capabilities, such as on-the-fly “soft” certificate pins.

However, CWAC-NetSecurity does not work automatically, the way the native Android 7.0+ network security configuration does. There are a few additional manual steps, illustrated in the same Trips/CW/StaticSecure sample project:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'com.apollographql.apollo:gradle-plugin:0.4.0'
    }
}

allprojects {
    repositories {
        jcenter()
        maven { url "https://s3.amazonaws.com/repo.commonsware.com" }
        maven { url 'https://maven.google.com' }
    }
}

dependencies {
  compile 'io.reactivex.rxjava2:rxjava:2.0.2'
  compile 'io.reactivex.rxjava2:rxandroid:2.0.0'
  compile 'com.android.support:recyclerview-v7:26.0.1'
  compile 'com.apollographql.apollo:apollo-rx2-support:0.4.0'
  compile 'com.squareup.okhttp3:okhttp:3.8.1'
  compile 'com.commonsware.cwac:netsecurity:0.4.4'
}

  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:networkSecurityConfig="@xml/network_security"
    android:theme="@style/Theme.Apptheme">
    <meta-data
      android:name="android.security.net.config"
      android:resource="@xml/network_security" />
    <activity
      android:name=".MainActivity"
      android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>

    TrustManagerBuilder tmb=
      new TrustManagerBuilder().withManifestConfig(getActivity());
    OkHttpClient.Builder okb=new OkHttpClient.Builder();

    try {
      OkHttp3Integrator.applyTo(tmb, okb);
      ApolloClient apolloClient=ApolloClient.builder()
        .okHttpClient(okb.build())
        .serverUrl("https://graphql-demo.commonsware.com/0.3/graphql")
        .build();

      observable=Rx2Apollo.from(apolloClient.query(new GetAllTrips()).watcher())
        .subscribeOn(Schedulers.io())
        .map(response -> (getAllTripsFields(response)))
        .cache()
        .observeOn(AndroidSchedulers.mainThread());
    }
    catch (Exception e) {
      Toast.makeText(getActivity(), "Um, we crashed!", Toast.LENGTH_LONG).show();
      Log.e(getClass().getSimpleName(), "Exception initializing network", e);
    }

NetCipher

In some cases, your users may be at risk merely by connecting to your server. HTTPS hides the data contained in your app’s communications with the server, but it does not hide the actual connection with the server itself.

In other cases, your users may have difficulty connecting with your server due to national firewall blocks (e.g., when countries elect to ban certain server types, such as social networks).

To help with these cases, the Guardian Project maintains NetCipher, and Orbot, to help Android apps use the Tor “onion routing” network. NetCipher has specific hooks to help you configure OkHttp to use an Orbot-supplied proxy server to be able to route your HTTPS communications over Tor.