Preventing Man-in-the-Middle Attacks in iOS with SSL Pinning
In this tutorial, you’ll learn how to prevent man-in-the-middle attacks using SSL Pinning and Alamofire. You’ll use the Charles Proxy tool to simulate the man-in-the-middle attack. By Lorenzo Boaro.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Preventing Man-in-the-Middle Attacks in iOS with SSL Pinning
25 mins
- Getting Started
- Understanding TLS
- The Three Phases of TLS Connections
- About Digital Certificates
- The Structure of a Digital Certificates
- Validating Digital Certificates
- SSL Certificate Pinning Under the Hood
- Why Do You Need SSL Certificate Pinning?
- Types of SSL Certificate Pinning
- Pinning in Alamofire 5
- Storing The Certificate
- Implementing Certificate Pinning
- Testing Certificate Pinning With Charles
- Certificate Pinning in Action
- Where to Go From Here?
Implementing Certificate Pinning
Before writing the code, you need to import the certificate that you previously downloaded. Open PinMyCert.xcodeproj in Xcode, if you don't still have it open.
Right-click on the root PinMyCert folder in the Project navigator. Click Add Files to “PinMyCert”..., then in the file chooser, find and select stackexchange.com.der and click Add.
Open NetworkClient.swift and paste the following code at the end of the file:
struct Certificates {
static let stackExchange =
Certificates.certificate(filename: "stackexchange.com")
private static func certificate(filename: String) -> SecCertificate {
let filePath = Bundle.main.path(forResource: filename, ofType: "der")!
let data = try! Data(contentsOf: URL(fileURLWithPath: filePath))
let certificate = SecCertificateCreateWithData(nil, data as CFData)!
return certificate
}
}
The above struct provides a user-friendly way to retrieve a certificate from the main bundle.
SecCertificateCreateWithData
is responsible for creating a certificate object from a DER-encoded file.
Still within NetworkClient.swift, find NetworkClient
and replace the entire line let session = Session.default
with the following code:
// 1
let evaluators = [
"api.stackexchange.com":
PinnedCertificatesTrustEvaluator(certificates: [
Certificates.stackExchange
])
]
let session: Session
// 2
private init() {
session = Session(
serverTrustManager: ServerTrustManager(evaluators: evaluators)
)
}
Here's the breakdown of the code above:
- You create a dictionary called evaluators, which contains a single key-value pair. The key is of type
String
and it represents the host you want to check. The value is of a subtype ofServerTrustEvaluating
calledPinnedCertificatesTrustEvaluator
. It describes the evaluation policy you want to apply for that specific host. You'll use thePinnedCertificatesTrustEvaluator
to validate the server trust. The server trust is valid if the pinned certificate exactly matches the server certificate. - You declare a private initializer that instantiates
Session
usingServerTrustManager
. The latter is responsible for managing the mapping declared in the evaluators dictionary.
Now, open ViewController.swift and find the code responsible for the network request:
NetworkClient.request(Router.users)
.responseDecodable { (response: DataResponse<UserList>) in
switch response.result {
case .success(let value):
self.users = value.users
case .failure(let error):
self.presentError(withTitle: "Oops!", message: error.localizedDescription)
}
}
Replace it with this new implementation:
NetworkClient.request(Router.users)
.responseDecodable { (response: DataResponse<UserList>) in
switch response.result {
case .success(let value):
self.users = value.users
case .failure(let error):
let isServerTrustEvaluationError =
error.asAFError?.isServerTrustEvaluationError ?? false
let message: String
if isServerTrustEvaluationError {
message = "Certificate Pinning Error"
} else {
message = error.localizedDescription
}
self.presentError(withTitle: "Oops!", message: message)
}
}
While the success case remains the same, you have enriched the failure case with an additional condition. First, you try to cast error
as an AFError
. If the cast succeeds, you'll evaluate isServerTrustEvaluationError
. If its value is true
, it means the certificate pinning has failed.
Build and run the app. Nothing should have changed visually.
But wait! If this is a tutorial that teaches you how to prevent man-in-the-middle attacks, how can you be sure you've done everything correctly when no attack has occurred?
To answer this question, jump right to the next section.
Testing Certificate Pinning With Charles
In order to verify that everything runs as expected, you first need to download the latest version of Charles Proxy, which is version 4.2.8 at the time of this writing. Double-click the DMG file and drag the Charles icon to your Applications folder to install it.
Charles is a proxy server, a middleware, that sits between your app and the computer’s network connections. You can use Charles to configure your network settings to route all traffic through it. This allows Charles to inspect all network events to and from your computer.
Proxy servers are in a position of great power, which also means they have the potential for abuse. That's why TLS is so important: Data encryption prevents proxy servers and other middleware from eavesdropping on sensitive information.
Charles generates its own self-signed certificate, which you can install on your Mac and iOS devices for TLS encryption. Since this certificate isn’t issued by a trusted certificate issuer, you’ll need to tell your devices to explicitly trust it. Once installed and trusted, Charles will be able to decrypt SSL events.
In your case, however, Charles won't snoop your SSL messages. Charles’ sneaky man-in-the-middle strategy won’t work because your pinning strategy will prevent it.
Certificate Pinning in Action
To see your enhanced security in action, launch Charles. Charles starts recording network events as soon as it launches.
In Charles, first switch to the Sequence tab. Then enter api.stackexchange.com in the filter box to make it easier to find the request that you need, and click the broom symbol to clear the current session.
Now click Proxy on the menu bar and select macOS Proxy to turn it back on (if it doesn’t already show a check mark).
Then, click Proxy ▸ SSL Proxying Settings and add api.stackexchange.com to the list. You can leave the Port field blank. Select Enable SSL Proxying and click OK to confirm.
Next, you need to install the Charles Proxy SSL certificate to allow proxying SSL requests in the Simulator.
In Charles, click Help ▸ SSL Proxying ▸ Install Charles Root Certificate in iOS Simulators. Then, on the Simulator, open the Settings app. Tap through General ▸ About ▸ Certificate Trust Settings (it's at the bottom, so you may have to scroll). Tap the switch to turn on the Charles Proxy CA and tap Continue in the resulting warning dialog.
Back in Xcode, build and run the project. You should see an alert like the following:
On Charles' side, you should see a failure like the one represented below:
Congratulations! You now have an app that is able to prevent man-in-the-middle attacks!
Where to Go From Here?
You can download the completed version of the project using the Download Materials link at the top or the bottom of this tutorial.
You’ve learned how to achieve SSL Certificate Pinning using Alamofire 5. Your users can now be sure attackers will not able to steal sensitive information from your apps.
When you've finished experimenting with your app and Charles, it's important to remove the Charles CA certficate from the Simulator. With the Simulator active, select Hardware ▸ Erase All Content and Settings... from the menu, then click Erase.
If you want to learn more about SSL Certificate Pinning and security in general, check out OWASP Mobile Security Testing Guide. It's a comprehensive testing guide that covers the processes, techniques and tools used during mobile app security tests.
In the meantime, if you have any questions or comments, please join the forum discussion below!