Categories
Security Swift

Client certificate with URLSession in Swift

Recently we I needed to implement an API in an app requiring a TLS client certificate.

It proved to be pretty simple, but I did need connect various bits and pieces together to get to a working solution. In this post I’ll show what worked for me.

To learn more about client certificate: https://en.wikipedia.org/wiki/TransportLayerSecurity#Client-authenticatedTLShandshake

Basically it involves a few things.

  • A client certificate, in my case packaged as a p12 file.
  • A password for the p12 files.
  • A cocoa pod called ASN1Decoder, to allow interpretation of ASN.1 formatted data.
  • An URLSessionDelegate
  • Once successfully obtaining the client certificate identity, we can create an URLCredential to respond to the challenge.

So here is the lowdown extracted from the session delegate I created:

public class MyURLSessionDelegate: NSObject, URLSessionDelegate {
    public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        // `NSURLAuthenticationMethodClientCertificate`
        // indicates the server requested a client certificate.
        if challenge.protectionSpace.authenticationMethod
             != NSURLAuthenticationMethodClientCertificate {
                completionHandler(.performDefaultHandling, nil)
                return
        }

        guard let file = Bundle(for: HTTPAccessURLSessionDelegate.self).url(forResource: p12Filename, withExtension: "p12"),
              let p12Data = try? Data(contentsOf: file) else {
            // Loading of the p12 file's data failed.
            completionHandler(.performDefaultHandling, nil)
            return
        }

        // Interpret the data in the P12 data blob with
        // a little helper class called `PKCS12`.
        let password = "MyP12Password" // Obviously this should be stored or entered more securely.
        let p12Contents = PKCS12(pkcs12Data: p12Data, password: password)
        guard let identity = p12Contents.identity else {
            // Creating a PKCS12 never fails, but interpretting th contained data can. So again, no identity? We fall back to default.
            completionHandler(.performDefaultHandling, nil)
            return
        }

        // In my case, and as Apple recommends,
        // we do not pass the certificate chain into
        // the URLCredential used to respond to the challenge.
        let credential = URLCredential(identity: identity,
                                   certificates: nil,
                                    persistence: .none)
        challenge.sender?.use(credential, for: challenge)
        completionHandler(.useCredential, credential)
        } else {
            completionHandler(.performDefaultHandling, nil)
        }
    }
}

As you can see, there is a lot of “if it fails we go to default” going on. This is security related code, so if things don’t work, we do not try any recovery, default handling just implies that no client certificate will be used and thus the connect should fail.

Here is the PKCS12 implementation. It is actually based on https://gist.github.com/algal/66703927b8379182640a42294e5f3c0b It is basically some helper code to bridge Core Foundation types into the memory safety of Swift.

private class PKCS12 {
    let label: String?
    let keyID: NSData?
    let trust: SecTrust?
    let certChain: [SecTrust]?
    let identity: SecIdentity?

    /// Creates a PKCS12 instance from a piece of data.
    /// - Parameters:
    ///   - pkcs12Data:
              the actual data we want to parse.
    ///   - password:
              The password required to unlock the PKCS12 data.
    public init(pkcs12Data: Data, password: String) {
        let importPasswordOption: NSDictionary
          = [kSecImportExportPassphrase as NSString: password]
        var items: CFArray?
        let secError: OSStatus
          = SecPKCS12Import(pkcs12Data as NSData,
                            importPasswordOption, &items)
        guard secError == errSecSuccess else {
            if secError == errSecAuthFailed {
                NSLog("Incorrect password?")

            }
            fatalError("Error trying to import PKCS12 data")
        }
        guard let theItemsCFArray = items else { fatalError() }
        let theItemsNSArray: NSArray = theItemsCFArray as NSArray
        guard let dictArray
          = theItemsNSArray as? [[String: AnyObject]] else {
            fatalError()
          }
        func f(key: CFString) -> T? {
            for dict in dictArray {
                if let value = dict[key as String] as? T {
                    return value
                }
              }
            return nil
        }
        self.label = f(key: kSecImportItemLabel)
        self.keyID = f(key: kSecImportItemKeyID)
        self.trust = f(key: kSecImportItemTrust)
        self.certChain = f(key: kSecImportItemCertChain)
        self.identity = f(key: kSecImportItemIdentity)

    }
}

Any question? I’ll gladly answer any questions you might have.

Categories
Xcode

Frequently used keyboard shortcuts I use in/with Xcode

As a mindful exercise I kept track of things I do in Xcode that might not seem obvious to a new user. Maybe all things are known. But who knows maybe there is a little gem in here. I use more shortcuts, but these I use most often by far.

Open Quickly dialog (⌘⇪O) with camel cased names

A lot of people know ‘⌘⇪O’, but not a lot of people are aware of the value of entering lowercase and uppercase input. Casing is significant in the “Open Quickly…” dialog. For example, if you have a class called SomeRandomClassName you can actually open that file by typing SoRaCl.

⌃6 to open the jump bar

Go ahead, try pressing ⌃6 in Xcode with an open Swift file. Now start typing.

Try ⌥ clicking things in the Xcode source editor

Helpful documentation in a pop-up, especially if you add doc comments. (Obviously uni can also display the Quick Help Inspector.

⌘⌥/ to add those doc comments

Having Doc comments on key areas of your interfaces really helps your peers work with the code you write. I like spending some time on my Doc comments to make ⌥-clicking extra useful.

⌘⇪J to display the current file in the Xcode navigator

It’s always easy to quickly jump to the current open file in the navigator to easily see and select related files.

⌥-drag a piece of text in the Xcode code exitor

It takes some getting used to, but once you get the hang of it, this will really save you time in specific editing situation. Try it!

Categories
Xcode

GitHub Actions for iOS projects

When building an iOS project, you probably want to have some form of continuous integration working on your codebase.

Continuous integration

Continuous integration (CI) is a process where each code change results in certain checks to be performed. It is best to automate this.

Continuous develivery/deployment

On top of integration, you most likely want to make sure your code changes become available to your users as well. To help in this process it is good to perform Continuous delivery (CD). CD is the process o automatically deploying your code changes when all checks have passed.

You need to run your CI/CD somewhere

In 2019 Github made GitHub Actions available. Recently I worked on getting CI/CD working on several codebases. The lessons learned I applied to the CocoaHeadsNL app. You can check the CocoaHeadsNL app’s entire codebase on Github, including it’s CI/CD process.

What are Github Actions

GitHub Actions allows us to run a workflow on any GitHub event on Linux, macOS, Windows, ARM, and Docker containers. You get live logs. It has a built in secret store. It is free (or cheap) depending on I you are an open Github repository or not.

In our case, it is good to know hosted runners can be a full macOS environment, which is required for xcodebuild.

To define a workflow you add a Yaml file at a well defined location within your repository: .github/workflows/[filename].yml

name: Deploy

on:
  # Trigger the workflow on push
  # but only for the master branch
  push:
    branches:
      - master
jobs:
  Build:
    runs-on: macOS-latest
    steps:
    - name: Dump file hierarchy
      run: ls -R

Steps involved

Your building blocks are called steps. And we need to:

  1. Checkout the code
  2. Compile, archive and codesign it
  3. Upload to Apple

Checkout the code

- uses: actions/checkout@v1

Compile, archive and codesign

- name: Select Xcode
  run: sudo xcode-select -switch /Applications/Xcode_11.3.app
- name: Xcode version
  run: /usr/bin/xcodebuild -version
- name: Build archive
  run: |
    xcodebuild -sdk iphoneos -project CocoaHeadsNL/CocoaHeadsNL.xcodeproj \
      -configuration Release -scheme CocoaHeadsNL \
      -derivedDataPath DerivedData \
      -archivePath DerivedData/Archive/CocoaHeadsNL archive
- name: Export Archive
  run: |
    xcodebuild -exportArchive \
      -archivePath DerivedData/Archive/CocoaHeadsNL.xcarchive \
      -exportOptionsPlist provisioning/App-Store.plist \
      -exportPath DerivedData/ipa

Upload to Apple

- name: Deploy App to Apple
  run: |
    xcrun altool --upload-app --type ios \
      --file DerivedData/ipa/CocoaHeadsNL.ipa \
      --username "${{ secrets.appstore_connect_username }}" \
      --password "${{ secrets.appstore_connect_password }}" --verbose

We need to do something with our “secrets”

More info on Github Actions’s Secret Store: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets.

  • Allows storing of string based data of max 64kb
  • Stored encrypted with a key stored with Github.
    • So don’t store your launch codes here people.
    • I chose to trust Github to some degree.

So what we need to do is:

  1. Encrypt files with GPG
  2. Add files to repo
  3. Decrypt files in your workflow
    • Password is stored as Github secret

Currently gpg is missing on macOS Hosted Runners

Fix it with this step:

- name: Install GPG
  run: brew install gnup

Decrypting files

#!/bin/sh

# Decrypt the files
# --batch to prevent interactive command --yes to assume "yes" for questions
gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" \
  --output provisioning/AppStoreCertificates.p12 provisioning/AppStoreCertificates.p12.gpg

gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" \
  --output provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision \
  provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision.gpg

# Three more lines decrypting a provisioning profiles ommited

# Install the provisioning profiles
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles

echo "List profiles"
ls ~/Library/MobileDevice/Provisioning\ Profiles/
echo "Move profiles"
cp provisioning/*.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
echo "List profiles"
ls ~/Library/MobileDevice/Provisioning\ Profiles/

security create-keychain -p "" build.keychain
security import provisioning/AppStoreCertificates.p12 -t agg \
  -k ~/Library/Keychains/build.keychain -P "$PROVISIONING_PASSWORD" -A

# install distribution cert and key
security list-keychains -s ~/Library/Keychains/build.keychain
security default-keychain -s ~/Library/Keychains/build.keychain
security unlock-keychain -p "" ~/Library/Keychains/build.keychain
security set-key-partition-list -S apple-tool:,apple: -s \
  -k "" ~/Library/Keychains/build.keychain

I will extend this post based on any questions I receive.

I also presented on GitHub Actions during the January 2020 CocoaHeadsNL meetup, check the video on Youtube: Building iOS apps with Github Actions, Jeroen Leenarts (English) during CocoaHeadsNL meetup January 2020

Categories
Xcode

Git based build number in Xcode

When building and uploading an iOS of Mac app AppStore connect demands you to upload each build of a specific version with a unique build number. If you have previously uploaded version 1.0 with build number 1. Your next upload for a version 1.0 should have a higher build number.

Basically a lot of hassle. Why not automate this

Start by creating a script file named set_build_number.sh and putting it in a folder called Scripts within the root of your Xcode project. In Xcode language $SRCROOT/Scripts/set_build_number.sh. Make sure to mark the script as executable by running chmod +x Script/set_build_number.sh.

#!/bin/bash

git=$(sh /etc/profile; which git)
number_of_commits=$("$git" rev-list HEAD --count)

target_plist="$TARGET_BUILD_DIR/$INFOPLIST_PATH"
dsym_plist="$DWARF_DSYM_FOLDER_PATH/$DWARF_DSYM_FILE_NAME/Contents/Info.plist"

for plist in "$target_plist" "$dsym_plist"; do
  if [ -f "$plist" ]; then
    /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${number_of_commits}" "$plist"
  fi
done

settings_root_plist="$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Settings.bundle/Root.plist"

if [ -f "$settings_root_plist" ]; then
  settingsVersion="$APP_MARKETING_VERSION (${number_of_commits})"
  /usr/libexec/PlistBuddy -c "Set :PreferenceSpecifiers:1:DefaultValue $settingsVersion" "$settings_root_plist"
else
  echo "Could not find: $settings_root_plist"
  exit 0
fi

Next add an extra build phase. Make sure you pick the one named New Run Script Phase and replace the shell script content with $SRCROOT/Scripts/set_build_number.sh.

The end result should look like the next screenshot.

What this does for you

This new build step does a few things:

  1. Obtain the number of commits on your current git branch.
  2. Put this value in your target’s info.plist as the APP_MARKETING_VERSION. Which is the value for “Build” in the general tab of your target.
Categories
Random thoughts

A new year a new start

My intention is to write a weekly post on something iOS, Xcode, Mac development related subject. I will also include the occasional post on CocoaHeadsNL, Do iOS conference and other related topics.

In the meantime other pages need to be updated.