Last-mile Security for gRPC-connected mobile APIs In , we evaluated for use in mobile applications. We took a look at common operations such as: Consider gRPC for Mobile APIs gRPC A basic request-response API callToken-based authenticationA single request, streaming response API call I concluded that the RPC function call paradigm felt more natural to me than designing and implementing a API implementation, and I said I wouldn’t hesitate to use gRPC with mobile clients when the API is static and well understood. fully-RESTful For mobile apps, 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. certificate pinning Certificate Pinning 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. Transport Layer Security (TLS) (Source: : image originally via Gary Stevens of ) Wikipedia — chain of trust 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 , use this same technique to intercept and potentially interfere with encrypted HTTPS streams. mitmproxy On mobile devices, 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: certificate pinning : Embedding the pins inside the distributed app package is usually the easiest to implement. Upgrading the pin set, though, requires an app field upgrade, so it is recommended to install multiple leaf and intermediate pins with each release. Preloading (TOFU): endpoints’ public keys are determined on first use after installation or upgrade. Security depends on the security of the environment at first use. Trust on First Use : Request pins from a trusted server. How to you trust the pin server? Perhaps you pin the pin server! If the pin server is stable, this allows you to manage a dynamic set of endpoints without requiring application upgrades for each change of served pins. Upgrades are only required if the pin server’s certificates change. It’s an interesting trade off and the subject of a future post. Pin Server 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, 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. Server Name Indication Pinning gRPC Managed Channels With gRPC, a client makes an to a interface which, through a , sends one or more proto request messages to and receives one or more response messages from the server. In , 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. rpc call stub channel Consider gRPC for Mobile APIs 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: <?xml version="1.0" encoding="utf-8"?> < resources > < integer-array name = "certs" > < item > @raw/localhost </ item > < item > @raw/otherhost </ item > </ integer-array > </ resources > 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: rawCerts = getResources().obtainTypedArray(certsId); ks = KeyStore.getInstance(KeyStore.getDefaultType()); InputStream certIS = getResources(). X509Certificate cert = (X509Certificate) cf.generateCertificate(certIS); } } } @Override int port = getResources().getInteger(R.integer.port); KeyStore certsKS = createPinnedKeyStore(R.array.certs); channel = PinnedChannelBuilder.build(host, port, serverHostOverride, certsKS); } // build a key store from a set of raw certificates KeyStore createPinnedKeyStore ( int certsId ) { TypedArray rawCerts = null ; KeyStore ks = null ; try { ks.load( null , null ); CertificateFactory cf = CertificateFactory.getInstance( "X.509" ); for (int i = 0 ; i < rawCerts.length(); ++i) { openRawResource(rawCerts.getResourceId(i, - 1 )); ks.setCertificateEntry( "cert" + i, cert); } catch (Exception e) { throw new RuntimeException(e); } finally { if (rawCerts != null ) rawCerts.recycle(); return ks; protected void onCreate ( Bundle savedInstanceState ) { super .onCreate(savedInstanceState); // ... // open grpc managed channel String host = getResources().getString(R.string.host); String serverHostOverride = "localhost" ; 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: ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forAddress(host, port) channelBuilder.overrideAuthority(serverHostOverride); } ((OkHttpChannelBuilder) channelBuilder).useTransportSecurity(); ((OkHttpChannelBuilder) channelBuilder). sslSocketFactory(getSslSocketFactory(certsKS)); } } throws Exception { TrustManagerFactory tmf = TrustManagerFactory. getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(certsKS); } } public class PinnedChannelBuilder { public static ManagedChannel build ( String host, int port, @Nullable String serverHostOverride, KeyStore certsKS ) { .maxInboundMessageSize( 16 * 1024 * 1024 ); if (serverHostOverride != null ) { // Force the hostname to match the cert the server uses. try { } catch (Exception e) { throw new RuntimeException(e); return channelBuilder.build(); private static SSLSocketFactory getSslSocketFactory(KeyStore certsKS) if (certsKS == null ) throw new RuntimeException( "No pinning Certificates found" ); // initialize trust manager factor from certs keystore // initialize SSL context from trust manager factory SSLContext context = SSLContext.getInstance( "TLS" ); context.init( null , tmf.getTrustManagers() , null ); // return socket factory from the SSL context return context.getSocketFactory(); Though cumbersome, this is not much different than building a custom socket factory for a restful HTTPS connection, although many networking stacks and have convenience methods to hide this complexity. Android N Trying It Out 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 ............................................++ ..................................................................++ -----. You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank ----- State or Province Name (full name) []: Locality Name (eg, city) []: Organization Name (eg, company) []: Organizational Unit Name (eg, section) []: Common Name (eg, fully qualified host name) []:localhost Email Address []: $ $ openssl req -newkey rsa: 4096 -nodes -keyout localhost.key -x509 -days 365 -out localhost.crt Generating a 4096 bit RSA private key writing new private key to 'localhost.key' For some fields there will be a default value, If you enter '.' , the field will be left blank. Country Name ( 2 letter code) []: 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 is an excellent resource for generating your own test certificate authority and longer key chains if you would like to explore these scenarios. certstrap tool Wrapping Up 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 , we have demonstrated: Consider gRPC for Mobile APIs A basic request-response API call Token-based authentication A single request, streaming response API call Pinned TLS connections to the server 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.