As
mobile applications store more and more local data, device theft is
becoming an increasing concern, especially in the enterprise. To ensure
that data cannot be obtained either by theft of the device or by a
network attacker, we’ll look at best practices for storing local data
securely. Developers must not rely on the “device encryption”
functionality of the iPhone 3GS; this mechanism is not robust against a
dedicated attacker (see www.wired.com/gadgetlab/2009/07/iphone-encryption), and special effort must still be made by the developer to keep data safe.
SQLite Storage
A
popular way to persist iPhone application data is to store it in an
SQLite database. When using any type of SQL database, you must consider
the potential for injection attacks. When writing SQL statements that
use any kind of user-supplied input, you should use “parameterized”
queries to ensure that third-party SQL is not accidentally executed by
your application. Failure to sanitize these inputs can result in data
loss and/or exposure.
Listing 1 shows the wrong way to write SQLite statements.
Listing 1. Dynamic SQL in SQLite
NSString *uid = [myHTTPConnection getUID]; NSString *statement = [NSString StringWithFormat:@"SELECT username FROM users where uid = '%@'",uid]; const char *sql = [statement UTF8String]; sqlite3_prepare_v2(db, sql, -1, &selectUid, NULL); sqlite3_bind_int(selectUid, 1, uid); int status = sqlite3_step(selectUid); sqlite3_reset(selectUid);
|
Here, the parameter uid
is being fetched from an object that presumably originates from input
external to the program itself (that is, user input or a query of an
external connection). Because the SQL string is concatenated with this
external input, if the string contains any SQL code itself, this will be
concatenated as well, thus causing unexpected results.
A proper, parameterized SQL query with SQLite is shown in Listing 2.
Listing 2. Parameterized SQL in SQLite
const char *sql = "SELECT username FROM users where uid = ?"; sqlite3_prepare_v2(db, sql, -1, &selectUid, NULL); sqlite3_bind_int(selectUid, 1, uid); int status = sqlite3_step(selectUid); sqlite3_reset(selectUid);
|
Not
only is this safer by ensuring that uid is numeric, but you’ll
generally get a performance boost using this technique over dynamic SQL
query construction. Listing 3 shows similar binding functions for other data types.
Listing 3. SQLite Binding
sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); sqlite3_bind_double(sqlite3_stmt*, int, double); sqlite3_bind_int(sqlite3_stmt*, int, int); sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); sqlite3_bind_null(sqlite3_stmt*, int); sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
|
Of course, now that Core Data
is supported in iPhone OS 3.0, this will likely become the preferred
method of data storage. Core Data internally saves information to a
SQLite database by default. Using Core Data is generally a good
approach, but it does remove some flexibility—an example would be using
custom builds of SQLite such as SQLCipher, which can provide transparent
AES encryption. Secure storage of smaller amounts of data can be done
with the Keychain.
iPhone Keychain Storage
The iPhone includes the Keychain
mechanism from OS X to store credentials and other data, with some
differences in the API and implementation. Because the iPhone has no
login password, only a four-digit PIN, there is no login password to use
for the master encryption key on the iPhone. Instead, a device-specific
key is generated and stored in a location inaccessible to applications
and excluded from backups.
The API itself is different
from the regular Cocoa API, but somewhat simpler. Rather than
secKeychainAddInternetPassword, secKeychainAddGenericPassord, and so on,
a more generic interface is provided: SecItemAdd, SecItemUpdate, and
SecItemCopyMatching.
Another
difference with the iPhone Keychain is that you can search for and
manipulate Keychain items by specifying attributes describing the stored
data. The data itself is stored in a dictionary of key/value pairs. One
thing common to the Keychain on both platforms, however, is that it’s
somewhat painful to use, considering most people just need to save and
retrieve passwords and keys.
Note
A complete list of available attributes is available at here.
The iPhone Keychain APIs only
work on a physical device. For testing in a simulator, one has to use
the regular OS X Keychain APIs. One reasonable simplification to this
process is by Buzz Andersen, at http://github.com/ldandersen/scifihifi-iphone/tree/master/security.
This code shows how to use a simple API for setting and retrieving
Keychain data, which uses OS X native APIs when built for a simulator
but iPhone APIs for a device build.
Shared Keychain Storage
With iPhone OS 3.0, the
concept of shared Keychain storage was introduced, allowing for separate
applications to share data by defining additional “Entitlements” . To share access to a Keychain between
applications, the developer must include the constant
kSecAttrAccessGroup in the attributes dictionary passed to SecItemAdd as
well as create an Entitlement.
The Entitlement should take
the form of a key called “keychain-access-groups” with an array of
identifiers that define application groups. For instance, an identifier
of com.conglomco.myappsuite could be added to all apps that Conglomco
distributes, allowing sign-on to Conglomco services with the same
credentials. Each application will also contain its own private section
of the Keychain as well.
What keeps other
applications from accessing your shared Keychain items? As near as I can
tell, nothing. This is another area where the App Store will probably
be relied upon to weed out malicious apps. However, until more details
are published about the proper use of shared Keychains, it is probably
prudent not to store sensitive data in them—which is to say, don’t use
them at all.
Adding Certificates to the Certificate Store
If
you need to work with the iPhone using Secure Sockets Layer (SSL) in a
test environment—and you should configure your test environment to use
SSL!—here are three different options you have:
Install your internal CA certificate on a machine that syncs to an actual iPhone via iTunes.
Retrieve the certificate from a web server using Safari.
Mail the certificate to the phone.
Because in all likelihood
you’ll be working primarily with the iPhone emulator, the second option
is your best bet. When accessing a certificate via e-mail or Safari, you
will be prompted with the “Install Profile” dialog (see Figure 1).
Clicking “Install” will store the certificate in the phone’s internal
certificate store. As of iPhone OS 3.0, you can remove these or view
certificate details (see Figure 2) by going to Settings | General | Profiles.