Last-mile Security for gRPC-connected mobile APIs
In Consider gRPC for Mobile APIs, we evaluated gRPC for use in mobile applications. We took a look at common operations such as:
I concluded that the RPC function call paradigm felt more natural to me than designing and implementing a fully-RESTful API implementation, and I said I wouldn’t hesitate to use gRPC with mobile clients when the API is static and well understood.
For mobile apps, certificate pinning is an important capability to strengthen API security, and in this sequel, we’ll examine certificate pinning for gRPC on Android. Spoiler alert - in the end, it’s quite similar to pinning a restful connection.
Transport Layer Security (TLS) is a well accepted and evolving standard to strengthen privacy and message integrity. When establishing a connection, a server endpoint sends its public key certificate to the requesting client. The client follows the certificate chain of trust until it reaches a root certificate it implicitly trusts.
Source: Wikipedia — chain of trust: image originally via Gary Stevens of HostingCanada.org
Android and iOS devices maintain an installed set of certificates which they implicitly trust. Unfortunately, it is too easy to trick mobile devices into trusting certificates signed by unexpected certificate authorities. Diagnostic tools, such as mitmproxy, use this same technique to intercept and potentially interfere with encrypted HTTPS streams.
On mobile devices, certificate pinning should be used to limit trust to website leaf certificates or only those intermediate or root authorities trusted by the app itself. You can pin against the certificates, their public keys, or hashes of their public keys. Options for storing these certificate or key pins include:
When verifying the pinning certificates, the client verifies both the certificate’s signature and the requested hostname. Because the same IP address may share multiple hostnames, Server Name Indication is a TLS extension which enables a client to request a specific certificate by virtual hostname. In addition to its use in virtual hosting, this technique simplifies debugging self-signed certificates served from localhost.
With gRPC, a client makes an rpc call to a stub interface which, through a channel, sends one or more proto request messages to and receives one or more response messages from the server. In Consider gRPC for Mobile APIs, we used a plain managed channel for our transport. To pin the channel, we will enable TLS (SSL) and create our own set of trusted certificates, separate from the certificates already installed on the device. We’ll use Android for our examples.
First, we will build our own keystore within our demonstration app. For convenience, we store a set of public key certificates as raw resources, and we identify those resources in a certs.xml resource file:
The keystore is created when the ShapesActivity is started, and the keystore and a server name override are passed to our pinned managed channel, PinnedChannelBuilder:
We override the server name so that the server will respond with an end-entity certificate whose common name (CN) matches what we have pinned.
The PinnedChannelBuilder we are building will use a customized SSLSocketFactory to make its connections. Java’s security and networking stack make this a bit tedious, requiring 1) building a javax.net.ssl.TrustManagerFactory containing our java.security.KeyStore, 2) creating a javax.net.ssl.SSLContext containing this TrustManagerFactory, and 3) exposing the javax.net.ssl.SSLSocketFactory from within the SSLContext:
Though cumbersome, this is not much different than building a custom socket factory for a restful HTTPS connection, although many networking stacks and Android N have convenience methods to hide this complexity.
To try it out, we use the same shapes demo application we used previously. We are running a gRPC server on localhost serving a shapes.proto API to a shapes app running in a local Android emulator.
First, we’ll generate a self-signed private key, public-key certificate pair for localhost using openssl:
The localhost.crt certificate file should be copied into the shape app’s raw resources directory (app/src/main/res/raw/). Similarly, we generate an otherhost.crt certificate file and also copy it to the raw resource directory.
We’ll install both the localhost.key private key and the localhost.crt certificate files into our gRPC server. gRPC servers are commonly configured for mutual-SSL. Our client is pinning the gRPC server and not the other way around, so ensure mutual SSL is disabled.
Now we fire up the app. The managed channel requests the localhost certificate from the server, and the channel is successfully pinned upon connection. Hitting the Stream button, we see the expected response:
To test the pinning, we delete the localhost certificate from the app and restart. This time the server delivers the localhost certificate, but the trust manager finds no matching pinned certificate, so the channel connection fails:
To keep it simple, we have only shown self-signed leaf certificates. A better practice would be to pin on intermediate certificates. Square’s certstrap tool is an excellent resource for generating your own test certificate authority and longer key chains if you would like to explore these scenarios.
On Android, we were able to demonstrate a pinned gRPC channel, and it really wasn’t any more difficult than pinning a restful HTTPS connection. Including Consider gRPC for Mobile APIs, we have demonstrated:
gRPC’s function call paradigm, along with gRPC’s ability to generate both client and server API interfaces for many target languages from a single proto file and our demonstration of basic and secure API functionality, makes gRPC a reasonable approach for mobile API development.