Try swift anastasiia voitova fb

Building a User-Centric Security Model in iOS Applications

Anastasiia Voitova talks about building security that does not fail when application keys are exposed, or when servers are hacked. Security that lasts as long as unique user’s crypto keys (or passwords) are safe. Putting secrets known by the user to be a source of trust is the ultimate way for apps to become “thin” in relation to a security model, thus lowering the risks and developer pain.

In this try!Swift talk, Anastasiia discusses a thin transparent security layers system and its applicability in client-server systems; and, of course, some latest changes in ATS.


Introduction (00:00)

I am Anastasiia and, for the last few years, I have talked about mobile security. In this talk, we will cover a real-world application as an example, and understand its risks and threat model. We will create a security system where most control is in our hands, as mobile developers. And no server failure will introduce new risks in this model. Be brave: it is a security talk, things may get a little bit complicated.

Why should we think more about security? Our users live in a magical world, where things just happen, and they think that they are safe out-of-the-box. But remember Pegasus? Jailbreak an iPhone by just pressing a link. That is a rare build, and we can do nothing about it, but it doesn’t mean that we shouldn’t make efforts on implementing security in our apps. Because without implementing a proper security strategy, attackers won’t need Pegasus. They will grab data from our application in a much easier way.

Why Should We Think More About Security? (00:55)

Most users trust sensitive data to our apps, regardless of how well we protect it. It is up to us to build a good security system. But, security people tend to look at the world through the lens of security, and most security talks pay attention to some methods, assuming and seeing our mobile application as a spherical cow; and security itself as a set of rules: do X, don’t do Y, Z, etc…” That’s not exactly the case. We are not growing spherical cows, we are building real applications for real users, and that is why we need to discuss security in a proper way.

Risk Model & Threat Model (03:56)

When building apps, we may start from a user problem, then move to planning the screen flow, filling this flow with features, and finally writing code. When we are talking about security, it is quite similar.

Security starts from a risks and threat model. After that, we move to secure methods and implementations, and finally choosing libraries and writing code. Note that the most popular question, “which library should I use,” is the end of the sequence. We have to think if our app is secure against a certain threat model and attacker. And from there, we derive all risks and measures.

Get more development news like this

We talked before about trust. What is a trust model? Trust model is whose secrets do you trust in your system? In our case, it is simple: everything’s broken, trust nobody, except the user.

Real-World Application (04:15)

For our real-world application, we need a real hero. Hector is our hero - he is our user. As soon as Hector installs our application, he puts some trust in our system. You may have sensitive data to protect, i.e. passwords, or private photos. Let’s imagine that Hector is a little bit of a paranoid user: he wants to protect his data; we will create application for users like him.

Our example app is a travel application. When you have a passport or insurance you usually have backups. When you are traveling somewhere, you want to store these backups in a safe way.

This is the app architecture: the user takes photos, the app sends these photos via a network to the server, and the server stores photos on some storage. Easy.

The threat model is not a very detailed one: we can imagine many possible threats: remote jailbreak, rubber-hose cryptanalysis, etc. Unfortunately, we cannot handle them all.

Let’s leave only the threats which are in our control. T1 and T4 mean the data may be stolen or changed; T2 means the network may be intercepted; T3 means the network may be intercepted, and data may be redirected. For every threat, we will create a countermeasure. Let’s do it, one-by-one!

T1/T4 Threats (07:20)

T1 and T4 are both about attackers who try to steal data or tamper it. You can do it easily on iPhone using applications like iPhone Bugs. On the server side, it is even easier. We should encrypt user’s data using symmetric encryption. The user has a secret, and we encrypt data using that secret. And if the user wants to decrypt data without knowing the secret, he cannot do it. Or at least he cannot do it without a trace.

T2 Threats (08:06)

Threat T2 is a passive man-in-the-middle, where the attacker collects data during transmission. It is easy to emulate using a proxy, like Charles or Burb, but it is also easy to do in the wild. As soon as we are talking transport, we mean asymmetric encryption, to ensure the identity of both parties talking. However, it is much safer to use asymmetric encryption to derive only one temporary symmetric key.

You might know the term “perfect forward secrecy,” or PFS. It means that the data is being split into sessions, and for each session, we derive a temporary key. And in case one key is leaked, you can decrypt only the data for one session; you cannot decrypt it all.

T3 Threats (09:05)

Threat T3 is an active man-in-the-middle. That is where attackers not only intercept the traffic but also redirect it and pretend to be our server. For example, we can name China with their Golden Shield Firewall, which is able to compromise SSL connection using fake SSL certificates. And again we are talking about asymmetric encryption, but this time, we are talking about asymmetric encryption with certification pinning.

You might have already implemented SSL pinning in your apps: the idea is that the app stores a public server certificate inside, and on every network connection, it compares the stored certificate with the one from the server. If certificates are different, the app rejects this network connection.

Protection Methods (10:13)

On slide 42, you can see a map of all our protection methods. They are:

  • Symmetric cryptography
  • Asymmetric cryptography with ephemeral keys
  • Asymmetric cryptography with certificate pinning

And it’s all about encryption. Encryption means keys; keys means trust.

Trust & Relationship Model (10:38)

Let’s go back our trust model. Whom do we trust? Only the user. Going back to our app:

  • Users take photos
  • Add photos to the app
  • The app encrypts the photos using symmetric encryption and wraps them into a container
  • The app and the server initiate a secure session, agree on ephemeral key and drops container into these keys
  • The app sends it to the server
  • The server unwraps the container and then stores the encrypted photos.

Please note that we only store the encrypted version.

There is one component I have not talked before on this scheme. There are cases where there is no trust between client and the server, or client and the transport, or all these things. There are methods to verify the server honestly, even if all cryptography fails.

ZKP - Zero-Knowledge Proof (11:59)

Zero-knowledge proof is a simple mathematical protocol about comparing a pre-shared secret between two parties.The two parties never transmit this secret via network, neither the secret itself, or any hashes derived from this secret. ZKP will help us to ensure that the server actually has our data.

You can read more about ZKP in this awesome blog post, zero-knowledge proof protocol without magic.

Data Model (12:50)

On slide 50, you can see what our data model looks like. We have a user password, and we derive a secret key from this password using KDF. KDF is a key-to-division function, another easy mathematical function. It is a way to create a regular string to a cryptographically strong key.

We also have the photo itself, and that is sensitive data. It should be private - we will encrypt it.

We have two pieces of metadata:

  • Metadata M1 is basically a timestamp, plus the CRC of the photo.
  • Metadata M2 is the name of the photo, which the application will ask the user to input.

These pieces of metadata are public, and we will use them for additional checks. Also both app and the server generate the key pair. Private + public keys, and remember that the application has a public server key inside.

On slide 51, you can see what our scheme looks like. The real password is only inside the user’s mind. Then we have a secret key, the sensitive data, two pieces of metadata, and the mobile key-pair and server key-pair.

And this is the hierarchy of trust. Everything has been derived from the user’s secret. We use a password to derive the user’s secret; we use the user’s secret for symmetric encryption. We also encrypt our keys, into some symmetric container, and then we use these keys for the transport-layer encryption. Everything is being derived from the password.

App flows (14:55)

Let’s see the application flow. The slides contain some pseudo-code to make it easier to understand, and I used the crypto primitives from the Themis Library.

  • SCell - Secure cell for symmetric encryption, to generate the symmetric containers.
  • SSession - Secure session for transport encryption.
  • SComparator - Secure comparator is the ZKP protocol implementation.

You can use any library that you prefer; however, the only ZKP implementation I am aware of on iOS is inside the Themis library. Let’s start one-by-one.

Send photo to server

  • Prepare data
    1. The application asks the user for the password, generates a secret key from this password, and encrypts the photo: EncData = SCell_wrap(SD, SK, Context=M2)
    2. Store encrypted data, grabs the metadata, asks the user to provide the name of the photo, and stores it, EncData, M1, M2.
    3. Removes the original photo from memory, SD, SK.
  • Transfer data
    1. The application starts a secure session connection to the server, using its private key and the server public key: Session = SSession(Priv(MKP), Pub(SKP))
    2. Sends encrypted information, application server derives one temporary ephemeral key, and uses it to encrypt the already encrypted container and to send encrypted photo and metadata to the server, EncData, M1, M2.
  • Terminate session
    1. Receives okay from the server.
    2. The application terminates the session. Which means that next time the user wants to send another photo, we will start another session, and generate another temporary key for it.
    3. We terminate the session and mark this data as synced on our side, EncData.

Read photo from server

  • Initialize a secure session connection
    1. We start it using the private key of the app and the public key of the server.
    2. On the second step, it is the trickiest part because the application needs to be sure that this server really contains the photo. It starts the ZKP procedure with the server. Basically, it sends metadata M1 to the server and asks the server to prove that it has metadata M2 without sending this metadata through the network. Meaning that the application sends a timestamp + CRC to the server and asks the server whether it has the name of the photo. The server should prove that it has the name of the photo, without sending it using the ZKP protocol. Only if this proof is successful, the application will ask the server to send the photo. Because if ZKP fails, it means that server cannot prove it has the photo, and we should not continue.
  • Transfer data
    1. Receive the encrypted data, EncData.
  • Decrypt data
    1. When the application receives the photo, it remembers that the photo is still encrypted, so it asks the user to provide the password to decrypt it: SD = SCell_unwrap(EncData, SK, Context=M2)
    2. And at last show the photo to the user.

Here are a few Swift code samples that might be helpful in understanding and implementing these security concepts.

Links:

Generate keys


// Generating EC keys
guard let keyGeneratorEC: TSKeyGen = 
TSKeyGen(algorithm: .EC) else {
    print("Error occurred while initializing object keyGeneratorEC")
    return
}

let privateKeyEC: NSData = keyGeneratorEC.privateKey
let publicKeyEC: NSData = keyGeneratorEC.publicKey

Symmetric encryption


let masterKeyData: NSData = self.generateMasterKey()
guard let cellSeal: TSCellSeal = TSCellSeal(key: masterKeyData) else {
    print("Error occurred while initializing object cellSeal", #function)
    return
}
let message: String = "All your base are belong to us!"
let context: String = "For great justice"

var encryptedMessage: NSData = NSData()
do {
    // context is optional parameter and may be ignored
    encryptedMessage = try cellSeal.wrapData(message.dataUsingEncoding(NSUTF8StringEncoding), context: context.dataUsingEncoding(NSUTF8StringEncoding))
    print("encryptedMessages = \(encryptedMessage)")
} catch let error as NSError {
    print("Error occurred while encrypting \(error)", #function)
    return
}

Symmetric decryption


let masterKeyData: NSData = self.generateMasterKey()
guard let cellSeal: TSCellSeal = TSCellSeal(key: masterKeyData) else {
    print("Error occurred while initializing object cellSeal", #function)
    return
}
let message: String = "All your base are belong to us!"
let context: String = "For great justice"

do {
    let decryptedMessage: NSData = try cellSeal.unwrapData(encryptedMessage, context: context.dataUsingEncoding(NSUTF8StringEncoding))
    let resultString: String = String(data: decryptedMessage, encoding: NSUTF8StringEncoding)!
    print("decryptedMessage = \(resultString)")
} catch let error as NSError {
    print("Error occurred while decrypting \(error)", #function)
    return
}

Initialize Session


guard let clientIdData: NSData = kClientId.dataUsingEncoding(NSUTF8StringEncoding), let clientPrivateKey: NSData = NSData(base64EncodedString: kClientPrivateKey, options: .IgnoreUnknownCharacters) else {
    print("Error occurred during base64 encoding", #function)
    return
}

self.transport = Transport()
self.transport?.setupKeys(kServerId, serverPublicKey: kServerPublicKey)
self.session = TSSession(userId: clientIdData, privateKey: clientPrivateKey, callbacks: self.transport)

Encrypt/Decrypt Session Messages


var encryptedMessage: NSData
do {
    guard let wrappedMessage: NSData = try
    self.session?.wrapData(message.dataUsingEncoding(NSUTF8StringEncoding)) else {
        print("Error occurred during wrapping message ", #function)
        return
    }
    encryptedMessage = wrappedMessage
} catch let error as NSError {
    print("Error occurred while wrapping message \(error)", #function)
    completion(data: nil, error: error)
    return
}

//...

do {
    guard let decryptedMessage: NSData = try self.session?.unwrapData(data),
        let resultString: String = String(data: decryptedMessage, encoding: NSUTF8StringEncoding)
else {
        throw NSError(domain: "com.example", code: -3, userInfo: nil)
    }
    completion(data: resultString, error: nil)
} catch let error as NSError {
    print("Error occurred while decrypting message \(error)", #function)
    completion(data: nil, error: error)
    return
}

Make It Tougher (19:00)

There are some ways to make it even tougher. For example, using not just one password for the app, but one password for every photo. Or maybe doing transport-layer-encryption, adding more checks to the transport layer.

Apple Security Practices (19:32)

What should you remember? Apple enforces good security practices for us, such as Apple transport security things. We need to use HTTPS almost everywhere and drop all ciphers.

There is a great blog post by Nabla explaining exactly what you need to change in your system to make it work. And all the new requirements for privacy settings, accessing the contact book, home kit, house kit, etc.

You can also check this slide from a Black Hat conference, where Ivan from Apple talked some serious security things.

Last, what I want to point out: Apple follows the same principles as our talk.

Conclusion (20:55)

  • User trust is simple to implement (eight steps, seven steps), and complicated to hack.
  • It will happen only if you think about security as a whole system, starting from risk models and threat model. Not as a set of separate methods.

  • Maybe you will need to read these slides, thinking about your new application.
  • Maybe you will want to read my other slides, where there are simple ideas about perfect forward secrecy, SSL pinning, ZKP.
  • Maybe you want to watch the latest WWDC videos. Just in the last year, there are three videos from WWDC about security and privacy. Apple really enforces it.
  • Maybe you want to read those awesome blog posts about Apple transport security, privacy settings, ZKP, or about the Pegasus spyware.

If you have any questions about building security, adding security in your applications or even what library should you use, just ping me.

Resources

Next Up: New Features in Realm Obj-C & Swift

General link arrow white

About the content

This talk was delivered live in September 2016 at try! Swift NYC. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.

Anastasiia Voitova

Anastasiia is a software engineer working at Stanfy. She’s been building iOS applications for several years, participating in the full application lifecycle: from gathering business demands and cost estimation, through UX prototyping, to developing and long-term supporting.

She went into computer security and cryptography when she was invited to fix a few lines of code in an iOS port of a cryptographic library, and ended up taking over all of iOS development and some general mobile ideology part of the project.

4 design patterns for a RESTless mobile integration »

close