Securing Communications on iOS

Securing Communications on iOS

Mobile
security has become a hot topic. For any app that communicates
remotely, it is important to consider the security of user
information that is sent across a network. In this post, you'll learn the current best practices for securing the communications of your iOS app in Swift. 

Use HTTPS

When developing your app, consider limiting network requests to ones
that are essential. For those requests, make sure that
they are made over HTTPS and
not over HTTP—this will help protect your user's data from "man in the middle attacks", where another computer on the network acts as a relay for your connection, but listens in or changes the data that it passes along. The trend in the last few years is to have all connections made over
HTTPS. Fortunately for us, newer versions of Xcode already enforce
this.

To create a simple HTTPS request on iOS, all we need to do is append "s" to the "http" section of the URL. As long as the host supports HTTPS
and has valid certificates, we will get a secure connection. This
works for APIs such as URLSession, NSURLConnection, and CFNetwork, as well as popular third-party libraries such as
AFNetworking.

App
Transport Security

Over
the years, HTTPS has had several attacks against it. Since it's
important to have HTTPS configured correctly, Apple has created App
Transport Security (ATS for short). ATS ensures that your app's network connections are using industry-standard protocols, so that you don't accidentally send user data insecurely. The good news is that ATS is enabled by default for apps built with current versions of Xcode.

ATS is available as of iOS 9 and
OS X El Capitan. Current apps in the store will not suddenly require
ATS, but apps built against newer versions of Xcode and its SDKs will
have it enabled by default. Some of the best practices enforced by ATS include using TLS version 1.2 or
higher, forward secrecy through ECDHE key exchange, AES-128
encryption, and the use of at least SHA-2 certificates.

It's important to note that while ATS is enabled automatically, it
doesn't necessarily mean ATS is being enforced in your app. ATS works
on the foundation classes such as URLSession and NSURLConnection
and stream-based CFNetwork interfaces. ATS is not enforced on lower-level networking interfaces such as raw sockets, CFNetwork sockets,
or any third-party libraries that would use these lower-level calls. So if you are using low-level networking, you'll have to be careful to implement ATS's best practices manually.

ATS Exceptions

Since ATS enforces the use of HTTPS and other secure protocols, you might wonder if you will still be able to make network connections that can't
support HTTPS, such as when you download images from a CDN cache. Not to worry, you can control ATS settings
for specific domains in your project's plist
file. In Xcode, find your info.plist file, right click it, and choose
Open As >
Source Code.

You
will find a section called NSAppTransportSecurity. If
it is not there, you can add the code yourself; the format is as
follows. 

This lets you change ATS settings for all network connections. Some of the common settings are as follows:

  • NSAllowsArbitraryLoads: Disables ATS. Don't use this! Future versions of Xcode will remove this key.
  • NSAllowsArbitraryLoadsForMedia: Allows loading of media without ATS restrictions for the AV
    Foundation framework. You should only allow insecure loads if your
    media is already encrypted by another means. (Available on iOS 10 and macOS 10.12.)
  • NSAllowsArbitraryLoadsInWebContent: Can
    be used to turn off the ATS restrictions from web view objects in
    your app. Think first before turning this off as it allows users to
    load arbitrary insecure content within your app. (Available on iOS 10 and macOS 10.12.)
  • NSAllowsLocalNetworking: This can be
    used to allow local
    network resources to be loaded without ATS restrictions.
    (Available on iOS 10 and macOS 10.12.)

The NSExceptionDomains dictionary lets you set settings for specific domains. Here is a description of
some of the useful keys you can use for your domain:

  • NSExceptionAllowsInsecureHTTPLoads: Allows the specific domain to use non-HTTPS connections.
  • NSIncludesSubdomains: Specifies if the current rules are passed down to subdomains.
  • NSExceptionMinimumTLSVersion: Used to specify older, less secure TLS versions that are permitted.

Perfect Forward Secrecy

While encrypted traffic is unreadable, it may
still get stored. If the private key used to encrypt that traffic is
compromised in the future, the key can be used to read all the
previously stored traffic. 

To prevent this kind of compromise,
Perfect Forward Secrecy (PFS) generates a session key that
is unique for each communication session. If the key for a
specific session is compromised, it will not compromise data from any
other sessions. ATS implements PFS by default, and you can control
this feature using the plist key NSExceptionRequiresForwardSecrecy. Turning this off will allow TLS
ciphers that don't
support perfect forward secrecy.

Certificate
Transparency

Certificate
Transparency is an upcoming standard designed to be able to check or
audit the certificates presented during the setup of an HTTPS
connection. 

When your host sets up an HTTPS certificate, it is issued
by what is
called a Certificate Authority (CA). Certificate Transparency aims at
having close to real-time monitoring to find out if a certificate was
issued maliciously or has been issued by a compromised certificate
authority. 

When a certificate is issued, the certificate authority must submit
the certificate to a number of append-only certificate logs, which
can later be cross-checked by the client and scrutinized by the owner
of the domain. The certificate must exist in at least two logs in order for the certificate to be valid.

The
plist key for this feature is NSRequiresCertificateTransparency. Turning this on will
enforce Certificate
Transparency. This is available on iOS 10 and macOS 10.12 and later.

Certificate and Public Key Pinning

When
you purchase a certificate to use HTTPS on your server, that
certificate is said to be legitimate because it is signed with a
certificate from an intermediate certificate authority. That certificate used by the intermediate authority might in turn be signed by another intermediate authority, and so on, as long as the last certificate is signed by a root
certificate authority that is trusted. 

When an HTTPS connection is
established, these certificates are presented to the client. This
chain of trust is evaluated to make sure the certificates are
correctly signed by a certificate authority that is already trusted
by iOS. (There are ways to bypass this check and to accept
your own self-signed certificate for testing, but don't do this in a production
environment.) 

If any of the certificates in the chain of trust are not valid, then the entire certificate is said to be invalid and your
data will not be sent out over the untrusted connection. While
this is a good system, it's not foolproof. Various weaknesses exist
that can make iOS trust an attacker's certificate instead of a
legitimately signed certificate. 

For example, interception proxies may possess an intermediate certificate that is
trusted. A reverse engineer can manually instruct iOS to accept their
own certificate. Additionally, a corporation's policy may have
provisioned the device to accept their own certificate. All of this
leads to the ability to perform a “man in the middle” attack on
your traffic, allowing it to be read. But certificate pinning will prevent
connections from being established for all of these scenarios.

Certificate pinning comes to the rescue by checking the server's certificate against a copy of the expected certificate.

In
order to implement pinning, the following delegate must be
implemented. For URLSession, use the following:

Or for NSURLConnection, you can use:

Both
methods allow you to obtain a SecTrust object from
challenge.protectionSpace.serverTrust. Because we are overriding the authentication
delegates, we must now explicitly call the function which performs
the standard certificate chain checks that
we've just discussed. Do
this by calling the SecTrustEvaluate function. Then we can compare the server's certificate with an expected one.

Here is an example implementation.

To use this code, set the delegate of the URLSession when creating your connection.

Make sure to include the certificate in your app bundle. If your
certificate is a .pem file, you will need to convert it to a .cer file in the macOS terminal:

openssl x509 -inform PEM -in mycert.pem -outform DER -out certificate.cer

Now, if the certificate is changed by an attacker, your app will detect it and refuse to make the connection.

Note that some third-party libraries such as AFNetworking
support pinning already.

Sanitization and Validation

With all of the protections so far, your connections should be pretty secure against man in the middle attacks. Even so, one important rule regarding network
communications is never to blindly trust the data you are receiving.
In fact, it's good programming practice to design by contract. The
inputs and outputs of
your methods have a contract that defines specific interface
expectations; if the interface says it will return an NSNumber, then
it should do so. If your server is expecting a string of 24 characters or fewer, make sure that the interface will only return up to 24
characters. 

This helps prevent innocent errors, but more importantly, it can also reduce the
likelihood of various injection and memory corruption attacks. Common
parsers such as the JSONSerialization class will convert text into Swift data types where these kinds of test can be done.

Other parsers may work with Objective-C equivalent objects. Here is a way to validate that
an object is of the expected type in Swift.

Before you send a delegate a method, make sure the
object is of the right type so
that it will respond to the method—otherwise the app will crash with an “unrecognized selector” error.

Additionally, you can see if an object conforms to a
protocol before trying to send messages to it:

Or you can check that it matches a Core Foundation object type.

It's a good idea to carefully choose which information from the
server the user can see. For example, it's a bad idea to display an
error alert that directly passes a message from the server.
Error messages could disclose debugging and security-related
information. One solution is to have the server send specific error codes that cause the client to show
predefined messages.

Also, make
sure you encode your URLs so that they only contain valid characters.
NSString’s stringByAddingPercentEscapesUsingEncoding will work. It
does not encode some characters such as ampersands and plus signs, but
the CFURLCreateStringByAddingPercentEscapes function allows
customization of what will be encoded.

Sanitizing User Data

When
sending data to a server, be extremely careful when any user input is
passed into commands that will be executed by an SQL server or a
server that will run code. While securing a server against such
attacks is beyond the scope of this article, as mobile developers we
can do our part by removing characters for the language that the
server is using so that the input is not susceptible to command
injection attacks. Examples might be stripping quotes, semicolons,
and slashes when they are not needed for the specific user input.

It
is good practice to limit the length of user input. We can limit the number of characters typed
in a text field
by setting the
UITextField's
delegate and then implementing its shouldChangeCharactersInRange
delegate method.

For a UITextView, the delegate method to implement this
is:

User input can be further validated so that the input
is of an expected format. For example, if a user is to enter an email
address, we can check for a valid address:

If a user is uploading an image to the server, we can
check that it is a valid image. For example, for a JPEG file, the
first two bytes and last two bytes are always FF D8 and FF D9.

The list goes on, but
only you as the developer will know what the expected input and
output should be, given
the design requirements.

URLCache

The
data you send over the network has the potential to be cached in
memory and on device storage. You can go to great lengths to protect
your network communications, as we have been doing, only to find out
that the communication is being stored. 

Various versions of iOS have
had some unexpected behaviour when it comes to the cache settings,
and some of the rules for what gets cached in iOS keep changing over
the versions. While caching helps network performance by reducing the
number of requests, turning it off for any data you think is highly
sensitive can be a good idea. You can
remove the shared cache at any time (such as on app startup) by
calling:

To
disable caching on a global level, use:

And if you are using URLSession, you can disable cache for the session like this:

If you
are using an NSURLConnection object with a delegate, you can disable
the cache per connection with this delegate method:

And to
create a URL request that will not check the cache, use:

Various
versions of iOS 8 had some bugs where some of these methods on their
own would do nothing. That means it's a good idea to implementing all of the above code for sensitive connections, when you need to reliably prevent caching of network requests.

The
Future

It is
important to understand the limits of HTTPS for protecting network
communications. 

In most cases, HTTPS stops at the server. For
example, my connection to a corporation's server may be over HTTPS,
but once that traffic hits the server, it is unencrypted. This means
that the corporation will be able to see the information that was
sent (in most cases it needs to), and it also means that company could
then proxy or pass that information out again unencrypted. 

I can't
finish this article without covering one more concept that is a
recent trend—what is
called "end-to-end encryption". A good example is an encrypted chat app
where two mobile devices are communicating with each other through a
server. The two devices create public and private keys—they exchange
public keys, while their private keys never leave the device. The data
is still sent over HTTPS through the server, but it is first encrypted
by the other party's public key in such a way that only the devices
holding the private keys can decrypt each other's messages. 

As an analogy to help you understand end-to-end encryption, imagine that I want someone to send me a message securely
that only I can read. So I provide them a box with an open padlock on it
(the public key) while I keep the padlock key (private key). The user writes
a
message, puts it in the box, locks the padlock, and sends it
back to me. Only I can read what the message is because I am the only
one with the key to unlock the padlock. 

With end-to-end encryption, the server
provides a service for communication, but it can not read the content
of the communication—they ship the locked box, but they don't have the key to open it. While the implementation details are beyond
the scope of this article, it's a powerful concept if you want to allow secure communication between users of your app. 

If you want to learn more about this approach, a place to
start is the GitHub repo for Open Whisper System, an open-source project.

Conclusion

Almost all mobile apps today will communicate across a network, and security is a critically important but often neglected aspect of mobile app development. 

In this article, we've
covered some security best practices, including simple HTTPS, application hardening of network
communications, data sanitization, and end-to-end encryption. These best practices should serve as a foundation for security when coding your mobile app.

And while you're here, check out some of our other popular iOS app tutorials and courses!

Source: Tuts Plus

About the Author