Add TLS and Certificate Pinning While Removing Client Secrets The tutorial demonstrates how to improve mobile app security by . In the tutorial, you work with a simple photo client which requires an API key to access . An API Proxy, introduced between your client and the picture service, removes the need for storing and protecting the API key on the client itself. Hands On Mobile API Security: Get Rid of Client Secrets removing vulnerable API secrets from mobile apps NASA’s picture of the day service Since I expected most people to run the tutorial with the Android client in an emulator and the proxy server on localhost, I deliberately ran plain HTTP protocol between client and proxy. Though this simplified the tutorial, it’s not appropriate for a production environment. To enhance security, you would want to run to protect against man in the Middle (MitM) attacks. HTTPS protocol with certificate pinning So in this sequel, you will generate a self-signed certificate during configuration and will modify the Android client to only accept connection requests from a server holding the certificate’s private key. TLS and Pinning establishes a secure connection between client and server offering both privacy and data integrity. is used to establish trust between client and server and establish secure communications. Transport Layer Security (TLS) Public Key Infrastructure (PKI) However, if an attacker can insert himself between client and server, he can intercept the initial TLS handshake, present his own valid PKI certificate, and establish a man in the middle interception point where he can read and alter communication between client and server. Certificate pinning establishes a white list of certificates that a client will accept. Even though a MitM attack might present a legal certificate, the client will only accept a connection that is signed by a known certificate. Getting Started To get started, download the updated Hands On API Proxy source code In a terminal or command window, change to a directory where you will store the tutorial, and clone this public git repository: . tutorials$ git clone https: //github.com/approov/hands-on-api-proxy.git Follow the additional set up instructions from the . original tutorial The steps directory contains working versions of the proxy and server code at each step of the tutorial, including working versions of the final pinned client and proxy projects. When you run the configuration setup, it will generate a self-signed digital certificate and corresponding private key, and it will place them in the appropriate pinned client and proxy project locations. Alternatively, you may use an existing certificate-private key pair, or you could generate your own self-signed certificate-private key pair using : OpenSSL $ openssl req -x509 -newkey rsa: -keyout key.pem -out cert.pem -nodes -days 4096 365 If you completed the previous tutorial, your pen directory should contain working client and proxy projects. If you are starting from a fresh repository, you should start with working copies of the secure client and enhanced proxy. Copy the secure client and enhanced proxy steps into your corresponding playpen directories, for example: api-proxy$ rm -rf pen/client api-proxy$ cp -r steps/client/android/ _secure-client pen/client api-proxy$ rm -rf pen/proxy api-proxy$ cp -r steps/proxy/node/ _enhanced-proxy pen/proxy 2 3 You can build the current pen projects and confirm that the client and proxy are working as expected. Adding HTTPS to the Localhost Proxy To start, ensure your digital certificate and private key pair are located in the proxy’s source directory. If you are using the certificates which were generated during configuration, they are named cert.pem and key.pem respectively: api-proxy$ cp steps/proxy/node/ _pinned-proxy/src/cert.pem pen/proxy/src/ api-proxy$ cp steps/proxy/node/ _pinned-proxy/src/key.pem pen/proxy/src/ 4 4 In the proxy pen, ensure that the required modules are installed: api-proxy$ cd pen/proxy api-proxy/pen/proxy$ npm install api-proxy/pen/proxy$ npm install https --save Next, modify the node proxy to run HTTPS. Require the https package, and replace the default HTTP server’s app.listen() call with a call to create an HTTPS server using the key pair. The modifications look like this: https = ( ); app = ( )(); https.createServer({ : fs.readFileSync(__dirname + ), : fs.readFileSync(__dirname + ) }, app).listen(proxyPort); // add https package const require 'https' const require 'express' // unchanged checking and app.use() not displayed... // start listening //app.listen(proxyPort, (err) => { // if (err) { // return console.log(chalk.red(`Unexpected error tryng to listen on ${proxyPort}:`, err)); // } // // console.log(chalk.green(`api proxy server is listening on ${proxyPort}`)); //}); key '/key.pem' cert '/cert.pem' // end of file Start the proxy server: api-proxy/pen/proxy$ npm start Try out your proxy using a browser to call the proxy. Assuming you are running locally on port 8080, replace a direct call to NASA with the now proxied call htttps://localhost:8080/api.nasa.gov/planetary/apod?date=2017–01–01. Using chrome, I see this response: Chrome does not trust self-signed certificates, so it warns us we are trying to run HTTPS with an untrusted certificate, If we proceed anyway using ADVANCED, we should see the correct response from the NASA server through the localhost API proxy: If you still do not see a valid JSON response, double check your proxy URL, network connectivity, and digital certificate. Triple check that the proxy URL specifies HTTPS protocol. Also in src/config.js, check and force the approov_enforcement value to false. Restart the proxy server, and you will see failing attestations in the proxy log, but the service will not be blocked. If you are using a certificate signed by a recognized , browsers will use the CA’s public key to verify the signature. Since the browser trusts the CA, if the signature is valid then the browser will in turn trust the certificate. certificate authority (CA) In contrast, self-signed certificates are easy and free to generate, but they are not trusted by most browsers or network client stacks. With a self-signed certificate, we must establish trust another way, typically by verifying that we received the expected digital certificate and that we recognize the host who sent it. To pin a connection in a production environment, you would likely use a certificate signed by a trusted CA, and that is actually the easier approach to implement. Using a self-signed certificate to pin will require a bit more work inside the client which we’ll tackle next. For reference, a completed version of the API proxy at this stage is in steps/proxy/android/4_pinned-proxy. Android Client Pinning In the Android client app, we want to pin the HTTPS channel to only accept a connection holding our digital certificate. Start by changing the api_url in config.xml to use HTTPS instead of HTTP: <resources> < https://10.0.2.2:8080/api.nasa.gov < = > string name "api_url" </ > string /resources> For networking, the Android client uses the . If our digital certificate is signed by a CA recognized by Android, the default trust manager can be used to validate the certificate. To pin the connection it is enough to add the host name and a hash of the certificate’s public key to the client builder(). See for an example. All certificates with the same host name and public key will match the hash, so techniques such as certificate rotation can be employed without requiring client updates. Multiple host name - public key tuples can also be added to the client builder(). OKHttp library this OKHttp recipe For the tutorial, we are using self-signed rather than CA-signed certificates. To establish trust with a self-signed certificate, you must create a custom TrustManager and also provide a method to verify the target host. OKHttp provides a . custom TrustManager recipe In the pen client project, begin by adding the self-signed certificate cert.pem into the app’s main assets directory. A copy of cert.pem can be found in steps/client/android/4_pinned-client/app/src/main/assets. In the App.java file, create a private SSLContextPinner class inside the App class which reads your digital certificate into a KeyStore which in turn initializes a new TrustManagerchain. The TrustManager chain then initializes the TLS SSLContext. Add methods to return the SSLContext and initial X509Trustmanagerfrom the chain: private { private SSLContext sslContext; private TrustManager trustManager; public SSLContextPinner( pemAssetName) { { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load( , ); InputStream certInputStream = getAssets().open(pemAssetName); BufferedInputStream bis = BufferedInputStream(certInputStream); CertificateFactory certificateFactory = CertificateFactory.getInstance( ); int idx = ; (bis.available() > ) { Certificate cert = certificateFactory.generateCertificate(bis); keyStore.setCertificateEntry( + ++idx, cert); Log.i( , + idx + + ((X509Certificate) cert).getSubjectDN()); } TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); trustManager = trustManagers[ ]; sslContext = SSLContext.getInstance( ); sslContext.init( , trustManagers, ); } (Exception e) { sslContext = ; trustManager = ; Log.e( , e.toString()); } } public SSLContext getSSLContext() { sslContext; } public X509TrustManager getX509TrustManager() { (X509TrustManager) trustManager; } }; /** * Creates an SSL context useful for pinning certificates. */ class SSLContextPinner String try null null new "X.509" -1 while 0 "" "App" "pinned " ": " 0 "TLS" null null catch null null "App" return return Next you must provide a host name verifier. Normally, you could compare the DNS name to the host name associated with the certificate, but with absolute IP addresses, this . Since we are pinning a known certificate in this example, we will blindly accept that the host name is valid for this tutorial. This is fine as long as no one has stolen our certificate’s matching private key, which is an acceptable risk for this tutorial; however, in production you would use a stronger check. For now, create an always true host name verifier inside the App class: technique breaks down in browsers and other network stacks private { @Override public boolean verify(final hostname, final SSLSession session) { ; } }; /** * Passes any host name verification. */ class NoHostnameVerifier implements HostnameVerifier String return true Use these classes for certificate pinning and host name checking to build a pinned client for your self-signed certificate. Modify the App class onCreate() method: public { @Override public onCreate (){ .onCreate(); mPlatformSpecifics = AndroidPlatformSpecifics( ); mAttestation = ApproovAttestation(mPlatformSpecifics); { SSLContextPinner pinner = SSLContextPinner( ); mClient = OkHttpClient.Builder() .sslSocketFactory(pinner.getSSLContext().getSocketFactory(), pinner.getX509TrustManager()) .hostnameVerifier( NoHostnameVerifier()) .addInterceptor( ApproovInterceptor(mAttestation)) .build(); } (Exception e) { Log.e( , e.toString()); Log.e( , ); IllegalStateException( ); } mDownloader = Picasso.Builder( ) .downloader( OkHttp3Downloader(mClient)) .build(); } } class App extends Application // ... void super new this new try new "cert.pem" new new new catch "App" "App" "Failed to pin connection" throw new "Failed to pin connection:" new this new With those changes, you are ready to test the pinned connection between client and server. Ensure that the proxy server is running, Build and run the modified client app on the Android emulator or device as before. You should see a gallery of current NASA photos in the emulator and photo requests in the API proxy console log. If you do not see photos, double check your proxy URL, network connectivity, and digital certificate. Double check the proxy IP address (normally10.0.2.2 from the Android emulator) and port address (normally set at 8080). In src/config.js, check and force the approov_enforcement value to false. Restart both proxy server and client app. On the proxy, you will see failing attestations, but the service will not be blocked. If you want to enforce client attestation checks, you must register the modified client app with the Approov demo service as you did in the . original tutorial For reference, a completed version of the API proxy at this stage is in steps/proxy/android/4_pinned-proxy. Not Another Secret? Congratulations on successfully pinning a self-signed certificate. At this point, you might be asking yourself “if the purpose of the tutorial was to remove secrets from the client app, didn’t we just add a new secret to the app?” Well, we did add a into the app, which was the certificate itself if using self-signing. However, the certificate is the public side of the public-private key pair. The API proxy server will give out this certificate to anyone who tries to establish a secure connection. It’s not a secret. The private key, held on the server and not on the client, is the true secret information. constant What is critical is to prevent anyone from adding or replacing your certificate with their own certificate and repackaging the app. Anyone using the modified app would then be vulnerable to a MitM attack. This is where client attestation such as is crucial. Any attempt to tamper with the app will result in an attestation failure. Approov In the Wild If you haven’t done so already, I recommend ensuring the is current and reregistering your final pinned client-proxy pair by following the final steps of the . This greatly strengthens your security. Approov demo package original tutorial Using HTTPS with pinning provides both privacy and data integrity. Pinning is an important technique to prevent man in the middle attacks, but it can be easily compromised if the client can be tampered with. In this case, it’s not a matter of hiding a secret but of ensuring that a public certificate cannot be modified in app. Using a client attestation service effectively removes this attack vector, restoring privacy and integrity to the connection. Going Forward Thanks for reading! I’d really appreciate it if you recommend this tutorial (by clicking the ❤ button) so other people can find it. To learn more about API security and related topics, visit or follow @critblue on twitter approov.io