Skip to content
Supakonoha edited this page Sep 6, 2020 · 6 revisions

We are going to create an iOS project that integrates SecureCommunications library and communicates with a backend that uses SecureCommunicationsVapor. Please, visit its Wiki to implement the backend example in Vapor.

Getting Started

Open Xcode 12 and create a new Project -> iOS -> App. Put the name you want, selecting SwiftUI on Interface, SwiftUI App on Life Cycle and Swift on Language and create it.

Add SecureCommunications

On File menu, select Swift Packages and Add Package Dependency. Introduce the URL of the library repository https://github.com/supakonoha/SecureCommunications and select Up to Next Major version 1.0.0 and add it to your app target.

Implement the call to hello endpoint

ContentViewModel

We are going to create a new Swift class into the project ContentViewModel that will manage the communication with the server:

import Foundation
import Combine
import SecureCommunications

class ContentViewModel: ObservableObject {
    @Published var message: String = "No Response"

    var publisher: URLSession.DataTaskPublisher?
    var cancellable: Cancellable? = nil

    init() {
        guard let url = URL(string: "http://192.168.0.2:8080/hello") else {
            self.message = "Error loading url"
            return
        }

        guard let publicKey = try? KeyStore().publicKeyInPemRepresentation() else {
            self.message = "Error loading my public key"
            return
        }

        guard let publicKeyBase64 = publicKey.data(using: .utf8)?.base64EncodedString() else {
            self.message = "Error loading my public key"
            return
        }

        var request = URLRequest(url: url)
        request.addValue(publicKeyBase64, forHTTPHeaderField: "X-PublicKey")

        publisher = URLSession.shared.dataTaskPublisher(for: request)

        hello()
    }
}

extension ContentViewModel {

    func hello() {
        message = "Loading"

        let senderPemPublicKey = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2qk3lQHFQ0PEp8/TcKAnD3VmGXOr
bpi8VRLHlfbq/yDTm32AOAtVQ7rF7dsKen0d+sRJxQIMG3Q+/wejozGhvA==
-----END PUBLIC KEY-----
"""
        guard let senderPublicKey = try? KeyStore.publicKey(pemRepresentation: senderPemPublicKey) else {
            self.message = "Error loading sender public key"
            return
        }

        let salt = "This is our salt"

        cancellable = publisher?.tryMap { element -> Data in
            guard let httpResponse = element.response as? HTTPURLResponse,
                  httpResponse.statusCode == 200 else {
                throw URLError(.badServerResponse)
            }

            return element.data
        }
        .receive(on: DispatchQueue.main)
        .sink(receiveCompletion: { (subscriber) in
            switch subscriber {
                case .failure(let error):
                    self.message = "Error: \(error.localizedDescription)"
                case .finished:
                    break
            }
        }) {[unowned self] (message) in
            guard let encryptedMessage = String(data: message, encoding: .utf8) else {
                self.message = "No Data Decrypted"
                return
            }

            guard let decryptedMessage = encryptedMessage.openAES(senderPublicKey: senderPublicKey, salt: salt) else {
                self.message = "No Data Decrypted"
                return
            }

            self.message = decryptedMessage
        }
    }
}

You should have into consideration:

  • In this class we define message that will be used on the view and updates the view when we receive data from server.
  • We are using 192.168.0.2 as IP address of our server. Please, modify with your server IP address.
  • We are adding a X-PublicKey header into the request where we are passing our public key in PEM representation and encoded on Base64. The server is waiting this header.
  • For decrypting we are using the server public key. This is an example but in your final project, please, manage it in a secure way.
  • The salt is the same in both parts

ContentView

We are modifying ContentView to use the ContentViewModel and update the text in the view when data is received

import SwiftUI

struct ContentView: View {
    @ObservedObject var viewModel = ContentViewModel()

    var body: some View {
        VStack {
            Text(viewModel.message)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Note

Remember, you cannot use Simulator with this app because Secure Enclave works only on real devices.

Testing our app

You can launch on a real device with iOS 14 and if everything works fine, you should see a Hello, world! on the screen.

Clone this wiki locally