1:12 This works fine, so we're ready to start making our app more secure.
1:26 For https, there's a long list of certificates trusted by the
operating system. These certificates are used to issue individual certificates,
like the one on our server. The problem is that any of the 150+ certificates on
the system's list can be compromised.
2:11 We can narrow the list of trusted certificates by pinning, meaning
we'll only trust our one certificate (or an intermediate certificate that we
2:39 We do so by adding a URLSessionDelegate, that will be asked for
information during the server handshake process. This delegate could be a
separate object, but for now we'll just use our view controller itself:
4:25 This method is called for all kinds of security checks, but since
we're only interested in the certificate challenge, we check for a so-called
trust in the challenge. This trust contains certificates and we check that it
contains at least one certificate. The trust object is a C data structure, so we
have to use a specific method to count the certificates:
7:12 If we have a trust, we want to pull the certificate out. There may
be multiple certificates, but they're in order, with the certificate closest to
us being first. So we only check the first certificate, using another C
7:53 If we get a certificate, we can check if it's one of ours by
comparing its content. We get the data as CFData, which is bridged to Swift's
Data, so we can simply cast it for easier use:
9:08 We compare the data against a list of our known certificates.
Because certificates expire, there may be some overlapping ones on the server.
The app should check if any of the trusted certificates is found. That's why we
use an array, which we'll populate later:
10:56 Back in the delegate method, we check if the found certificate
data is contained in our certificates array. Finally, we have to call the
completion handler to either "use a credential", if we find a trusted
certificate, or "cancel" otherwise. We can simply create a credential with the
trust we found earlier, in order to advance successfully. The complete delegate
13:14 We're still checking against an empty certificates array, but it's
good to check that the challenge fails if we want it to.
Storing a Certificate
13:52 Now we need our certificate's data. We use openssl in the Terminal
to retrieve it from our server. Specifically, we use the s_client command,
which can connect to any server over SSL, by specifying the server address and
port 443. The < /dev/null part redirects back in, otherwise we'd be talking to
16:11 This does work and we receive a lot of data, including a list of
three certificates — we confirm that we see the part of the first certificate
listing that says CN=, which stands for common name:
0 s:/OU=GT09961371/OU=See www.rapidssl.com/resources/cps (c)15/OU=Domain Control Validated - RapidSSL(R)/CN=www.objc.io
i:/C=US/O=GeoTrust Inc./CN=RapidSSL SHA256 CA - G3
1 s:/C=US/O=GeoTrust Inc./CN=RapidSSL SHA256 CA - G3
i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
16:33 Below that, we can see the actual certificate data we need. To
copy the certificate into a file, we use openssl again. We repeat the previous
command and pass its output to openssl x509 (the general format name of
certificates), we specify the binary output format, DER, and we output it to a
new file, named
17:27 The saved file's size should be greater than zero; around 1 kB.
17:44 We add the certificate file to our project, using menu option
File > Add Files, which automatically adds the file to the project's target.
18:32 Finally, we load the certificate file's contents into the view
controller's array of trusted certificates. By initializing the array with a
closure, the property can stay immutable. We force unwrap optionals, because if
reading the file fails, we want the app to crash:
21:01 Our application now makes sure the server it connects to is
really our server. This protects the user from man-in-the-middle attacks, where
a fake certificate gets pushed onto their device, making the system trust a
potentially malignant server. It also protects us in case any of Apple's trusted
certificates get compromised.
22:04 By default, we need to have a commercial certificate in order to
pass ATS, the transport security system. We could allow arbitrary URLs and use a
self-signed certificate, but we'd have to undergo extra review by Apple before
they approve our app.
22:56 We've seen the most simple way to implement certificate pinning.
We're just checking that the one certificate of our request equals the one on
our server. A more advanced option is to also check intermediate certificates.
23:21 It can be tough to work through the documentation of the
necessary C functions, to figure out what you're supposed to do with them. Rob
wrote some open source code
that offers more flexibility to the developer and not only compares, but
evaluates certificates. This is a more full-proof starting point, than figuring
everything out from scratch.