The CommonsBlog


Certificate Pinning and Failing Open

SSL is nice, but there are risks of fraudulent certificates being issued by rogue certificate authorities. One approach to dealing with this risk is to use certificate pinning, teaching your Android app details about the expected SSL certificate, so the app can fail if it encounters a fraudulent one. This reduces the risk of somebody with a fake cert intercepting your communications. However, it can increase other risks, as Square’s Jesse Wilson pointed out earlier today.

For example:

  • If you pin to your own certificate, that works, until you need to replace that certificate, because it is expiring or for other reasons (e.g., the Heartbleed attack “OMG replace all teh certs!” response)

  • If you pin to some other certificate up the chain towards the CA’s root certificate, that works, until the intermediary needs to replace the certificate

I imagine that this is why Android 7.0’s network security configuration’s certificate pinning implementation fails open. In other words, when you put an expiration date on a pinned certificate, when that date arrives, that pin is ignored. For planned certificate obsolescence — such as the certificate’s own expiration date — this will allow your app to keep working, even for those users who failed to update their app. Users who update their app can be moved over to a new pin for a new certificate. Since CWAC-NetSecurity is a backport of Android 7.0’s network security configuration, it too supports expiring pins.

If you elect to pin certificates, whether using network security configuration or something else:

  • Set an expiration date on the pin, and fail open. Yes, this reduces security, but only for those users running older editions of your app.

  • Have a plan for an emergency app update, in case you need to replace the server’s SSL certificate in a hurry (e.g., Heartbleed-style scenarios).

For a server that only handles mobile apps, not Web browsers, you could avoid some of this by switching to a self-signed certificate and effectively “pinning” to that. Since there is no certificate authority in the mix, you are not at risk of a certificate authority screwing up, being hacked, or being suborned into issuing a fraudulent certificate. Remember: the rationale behind a certificate authority is to help the user with a generic client app (Web browser) determine if a certificate is valid. It does not add as much value in scenarios with a specific client, such as an Android app dealing exclusively with your server.

Nov 28, 2016


Random Musings on the 7.1 Developer Preview 2

Each time Google releases a new developer preview, I try to talk about the changes that may not be obvious when reading the high-level descriptions.

However, in this case, there does not seem to be much that is different that affects most developers, beyond the items that I mentioned in my Developer Preview 1 post. Near as I can tell, this is just bug fixes, which makes sense, if the rollout of 7.1 to non-Pixel phones is only weeks away.

Nov 22, 2016


Consuming Content? Be Flexible!

Many times, when we work with a content Uri, we get an InputStream, read in the content, and we’re good to go.

Sometimes, though, consumers of content make some unfortunate assumptions.

One assumption is that you can pass rw in for the mode on openFileDescriptor() and kin. First, this assumes that you have write access, which may or may not be true. Plus, rw requires that the content that you are working with be stored as a plain file on the filesystem somewhere.

Another related assumption is that the InputStream is seekable, supporting mark() and reset(). Once again, this only works if the content in question is backed by a plain file on the filesystem.

However, increasingly, that will not be the case, as more and more developers turn to publishing content, to get around things like the file Uri ban in Android 7.0. While content may be backed by a plain file on the filesystem, it might also be backed by:

  • A file, but one that needs to be transformed on the fly by the provider (e.g., decrypted)

  • A piece of a file (e.g., an asset packaged in the app’s APK)

  • Something in memory (e.g., the contents of a BLOB column that was read in from a database)

  • Something being streamed from the Internet (though this is kinda risky IMHO, on the provider’s part)

The ContentProvider publishing this content will be using something other than a file-based ParcelFileDescriptor, usually in the form of a pipe or socket pair. Those do not support rw mode, nor do they support seeking.

In particular, if your app is usable with read-only non-seekable content, but perhaps with a subset of functionality, make sure that you support it. For example, a couple of Android PDF viewers — including Google’s Drive PDF Viewer — seem to require rw access and crash in situations where other apps (e.g., Adobe Reader) work fine. Gracefully degrade in these cases: try for rw access, and then try for r access if the rw attempt fails.

If seekability is the issue, more so than rw access, you can try calling getStatSize() on the ParcelFileDescriptor that you get back from openFileDescriptor() on the ContentResolver. If this returns -1, then there is a good chance that the stream is not file-backed and will not support seeking. If this returns 0 or a positive value, that should be the size of the file that the stream is delivering to you.

If you need a test case for these cases, toss my StreamProvider in your test suite and test against an asset served via a content Uri. This will be a non-seekable stream on which rw is not an option (as you cannot modify an asset).

The majority of the time, when you get a content Uri, it will be served by a provider that is using a file for the content. In that case, rw mode may be available, and seeking should work. Just do not assume that you can always do that for every Uri. Make sure that you test cases where the Uri is something else, and that you handle those cases as best as you can.

Nov 21, 2016


Be Careful with Scoped Directory Access

Android 7.0 added scoped directory access. This API allows you to ask the user for blanket access to a common public directory on an arbitrary StorageVolume, such as a USB OTG drive or other removable storage. If the user grants access, you get a Uri back, akin to as if you had used ACTION_OPEN_DOCUMENT_TREE and the user happened to have chosen that particular directory. The difference is that you are choosing the directory, from a list of candidates, rather than granting the user full freedom to choose any directory or other “document tree” that the user wants.

However, there is a UX flaw with scoped directory access.

The flow of the permission dialogs resembles that of Android 6.0’s runtime permissions:

  • When you first ask for access, the user can allow or deny

  • If the user denied access, and you later ask for access again, the dialog now has a “Don’t ask again” checkbox

  • If the user checked that checkbox and denied access again, any future attempts you make to request access will be denied immediately, without the user seeing a dialog

One problem is that we have no good way of knowing that the user has previously denied our request, let alone checked the “Don’t ask again” checkbox. With Android 6.0’s runtime permissions, we have checkSelfPermission() and shouldShowPermissionRequestRationale() for those things. We have no equivalents for scoped directory access.

However, the bigger problem is that once the user checks “Don’t ask again” and denies access, the user has no further recourse. With runtime permissions, the user can always go into the Permissions area of your app’s page in Settings and manually grant permissions. There is no equivalent of this for the scoped directory access API.

On one device (a Nexus 5X running the 7.1 preview), the user can use “Clear Data” to reset these dialogs, causing future dialogs to appear again even if “Don’t ask again” had been checked. Of course, “Clear Data” has somewhat broader impact than this, and the user might not appreciate wiping out all the app’s local data.

Worse, on two test devices (a Nexus 5X running 7.0 and a Google Pixel running 7.1), not only does “Clear Data” not fix this, but a full uninstall of the app does not fix this. AFAICT, nothing short of a factory reset would allow the app to ask the user for permission and the user have an opportunity again to grant permission.

Admitttedly, this is an edge case, but it is one that you should keep in mind if you are using createAccessIntent() and the scoped directory access API. I filed this issue to try to get some resolution to how the user is supposed to manually revert the “Don’t ask again” status.

Nov 18, 2016


The Busy Coder's Guide to Android Development Version 8.1 Released

Subscribers now have access to the latest release of The Busy Coder’s Guide to Android Development, known as Version 8.1, in all formats. Just log into your Warescription page and download away, or set up an account and subscribe!

This time around, I:

  • Added a new chapter on the basic steps for writing Gradle plugins

  • Added a new chapter on compile-time code generation, by means of a Gradle plugin and Square’s JavaPoet

  • Added a new chapter on updating your app’s code dynamically, as a counterpart to updating the app’s code by publishing a new APK

  • Added a new chapter on Android 7.1’s app shortcuts, both the static kind (which can be used by pre-7.1 home screens) and the dynamic kind

  • Added a new chapter on Java 8 lambda expressions, enabled via the Jack compiler

  • Added a new section to the RecyclerView chapter on using DiffUtil to identify and animate changes to the contents of a RecyclerView

  • Overhauled the chapter on NetCipher

  • Added a bit more material on ConstraintLayout

  • Made other minor improvements and errata fixes

Also, the APK edition of the book has a new appinar on drag-and-drop.

Barring some major code drop from Google here in the waning weeks of 2016, this is the eighth and final update for this year, adding about 500 pages of material, along with the first 24 appinars.

The next update is tentatively scheduled for early January 2017.

Nov 14, 2016


Older Posts