Categories
Podcast Random thoughts

Podcasting gear

That didn’t take very long. After publishing my first episode I got a question almost right away on what I used to record my show.

Here’s the list:

I took a while picking and selecting what to get. I initially wanted a Audio-Technica ATR2100x-USB but it has been unavailable everywhere for a couple of weeks now. So I just bit the bullet and went for a higher spec setup. I specifically went for a cardioid dynamic microphone and not a condenser. I wanted more of that broadcast sound and at the same time avoid picking up unwanted background noise as much as possible. A condenser, or so I read, picks up way more background noise.

Marco Arment’s microphone blog post was very helpful in getting to know some basics and find a good microphone amp suggestion.

For hosting I use Buzzsprout.

All product links are affiliate links.

Categories
Podcast Random thoughts

AppForce1: First episode is live

Categories
Podcast Random thoughts

AppForce1 podcast feed availability

My new podcast is now listed on Apple Podcasts as well. This means you can now find my podcast in your favourite podcast player app by searching for “AppForce1” and get my first episode as soon as it is available next week.

Categories
Podcast Random thoughts

AppForce1 podcast

I’m working on launching my new podcast. Working title “AppeForce1 podcast”.
Just playing with editing and sound bites a little bit. Here’s an initial promo.

Categories
Random thoughts Swift

Tuist.io

At the Dutch CocoaHeads meetup in september I presented about Tuist.

Our path to micro frameworks using Tuis.io

In this video I show how we at Achmea changed the architecture of our app to use µ frameworks with Tuist.io. I demonstrates how to create a sample app and shows the new structure of the ‘Even Appen’ app.

Categories
Random thoughts Swift

Github Actions and MacOS based Actions charged at 10x

Something I forgot to mention on my previous post on GitHub Actions.

GitHub charges 10 build minutes for each MacOS build minute. Be aware of this when working with GitHub Actions for your Mac based builds. It is probably worth it to try and off load as much work as possible to a Linux based image.

SwiftLint and other code analysis is a perfect example of examples of work to be offloaded to a Linux image.

Categories
Security Swift

Store data, encrypted

Today I want to share a helper class I worked on a while back. The requirement was that some (large) piece of data needed to be stored on the device, it should be stored encrypted. File protection complete was deemed not enough.

The idea was that a piece of data needed to be stored on disk, with a key stored in the keychain, in the secure enclave if available.

Just a few lines, but there’s actually a lot going on. The code created was inspired by various bits and pieces online and it took a bit of trial and error to get right.

First things first. We need to determine if a Secure Enclave is available and functional.

import Foundation
import os.log

enum CryptoError: Error {
    case keyCreationFailed
}
class Crypto {
    
    static var shared: Crypto {
        return self.sharedInstance
    }
    
    private static let sharedInstance = Crypto()
    
    private let keyName = "disk_storage_key"
    
    private let hasSecurityEnclave: Bool
    private let algorithm: SecKeyAlgorithm
    private let keySize: Int
    private let keyType: CFString
    
    init() {
        var attributes = [String: Any]()
        attributes[kSecAttrKeyType as String] = kSecAttrKeyTypeEC
        attributes[kSecAttrKeySizeInBits as String] = 256
        attributes[kSecAttrTokenID as String] = kSecAttrTokenIDSecureEnclave
        
        var error: Unmanaged?
        let randomKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)
        
        if randomKey != nil {
            self.hasSecurityEnclave = true
            self.keySize = 256
            self.keyType = kSecAttrKeyTypeEC
            self.algorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
        } else {
            self.hasSecurityEnclave = false
            self.keySize = 4096
            self.keyType = kSecAttrKeyTypeRSA
            self.algorithm = .rsaEncryptionOAEPSHA512AESGCM
        }
    }
}

That actually quite a lot of code already isn’t it. But if you follow along in the above code, it’s not that complicated. We just try to create a random key that requires the Secure Enclave. To my knowledge this generates a key pair, but does not store anything in the keychain or the Secure Enclave. Based on the result, a couple properties are stored on the Crypto class.

Generating a new key pair

Next up is generating a key pair and actually hanging on to it.

private func makeAndStoreKey(name: String) throws -> SecKey {
        
        guard let access =
            SecAccessControlCreateWithFlags(
                kCFAllocatorDefault,
                kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                self.hasSecurityEnclave ? SecAccessControlCreateFlags.privateKeyUsage : [],
                nil) else {
                    throw CryptoError.keyCreationFailed
        }
        var attributes = [String: Any]()
        attributes[kSecAttrKeyType as String] = self.keyType
        attributes[kSecAttrKeySizeInBits as String] = self.keySize
        #if targetEnvironment(simulator)
        print("SIMULATOR does not support secure enclave.")
        #else
        if self.hasSecurityEnclave {
            attributes[kSecAttrTokenID as String] = kSecAttrTokenIDSecureEnclave
        }
        #endif

        let tag = name
        attributes[kSecPrivateKeyAttrs as String] = [
            kSecAttrIsPermanent as String: true,
            kSecAttrApplicationTag as String: tag,
            kSecAttrAccessControl as String: access
        ]

        var error: Unmanaged?
        let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)

        if let error = error {
            throw error.takeRetainedValue() as Error
        }

        guard let unwrappedPrivateKey = privateKey else {
            throw CryptoError.keyCreationFailed
        }

        return unwrappedPrivateKey
    }

Why is using these keychain functions so verbose? A lot of stuff going on. But if you read through it carefully you will see that, again, a key pair is generated, but now we actually hang on to it. See that kSecAttrIsPermanent being give a value of true?

Also notice that there is some code to deal with a simulator not having a Secure Enclave.

Loading a key

We did most of the hard work already now. We also need some code to load a key. We’ll get to choosing between loading and generating in a bit. Key take away in the next bit is that the resulting SecKey is optional. We just might try and load a key and if it fails, we create one…

    private func loadKey(name: String) -> SecKey? {
        let tag = name
        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: tag,
            kSecAttrKeyType as String: self.keyType,
            kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
            kSecReturnRef as String: true
        ]

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        guard status == errSecSuccess else {
            return nil
        }
        return (item as! SecKey) // swiftlint:disable:this force_cast
    }

Let’s encrypt

Now we are getting to the good part. Let’s encrypt (pun intended).

func encrypt(data clearTextData: Data) throws -> Data? {
        let key = try loadKey(name: keyName) ?? makeAndStoreKey(name: keyName)

        guard let publicKey = SecKeyCopyPublicKey(key) else {
            // Can't get public key
            return nil
        }
        let algorithm: SecKeyAlgorithm = self.algorithm
        guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, algorithm) else {
            os_log("Can't encrypt. Algorithm not supported.", log: Log.crypto, type: .error)
            return nil
        }
        var error: Unmanaged?
        let cipherTextData = SecKeyCreateEncryptedData(publicKey, algorithm,
                                                       clearTextData as CFData,
                                                       &error) as Data?
        if let error = error {
            os_log("Can't encrypt. %{public}@", log: Log.crypto, type: .error, (error.takeRetainedValue() as Error).localizedDescription)
            return nil
        }
        guard cipherTextData != nil else {
            os_log("Can't encrypt. No resulting cipherTextData", log: Log.crypto, type: .error)
            return nil
        }

        os_log("Encrypted data.", log: Log.crypto, type: .info)
        return cipherTextData
    }

Did you notice the optional chaining on the loadKey call?

Let’s decrypt

Having encrypted data is fun and all, but we do need some way to decrypt it again. Notice that in the next bit, being unable to load a key is a failure. When decrypting we do expect that a key is already present.

func decrypt(data cipherTextData: Data) -> Data? {
        guard let key = loadKey(name: keyName) else { return nil }

        let algorithm: SecKeyAlgorithm = self.algorithm
        guard SecKeyIsAlgorithmSupported(key, .decrypt, algorithm) else {
            os_log("Can't decrypt. Algorithm not supported.", log: Log.crypto, type: .error)
            return nil
        }

        var error: Unmanaged?
        let clearTextData = SecKeyCreateDecryptedData(key,
                                                      algorithm,
                                                      cipherTextData as CFData,
                                                      &error) as Data?
        if let error = error {
            os_log("Can't decrypt. %{public}@", log: Log.crypto, type: .error, (error.takeRetainedValue() as Error).localizedDescription)
            return nil
        }
        guard clearTextData != nil else {
            os_log("Can't decrypt. No resulting cleartextData.", log: Log.crypto, type: .error)
            return nil
        }
        os_log("Decrypted data.", log: Log.crypto, type: .info)
        return clearTextData
    }

Conclusions

A couple things to notice in all of the above.

Categories
Random thoughts

Start of the Corona crisis

This won’t be a technical write-up. Just some thoughts on current events.

I’m sitting here in my bed. Not able to sleep. So much has happened. A lot went well. I am very lucky that my job as an iOS developer allows me to work remote so easily. It has been a crazy two weeks. The Netherlands went from business as usual to Corona crisis mode. In hindsight, the writing was on the wall. But man, what an experience it has been.

My day job switched to working fully remote. And things have transitioned relatively smooth. We are in the middle of planning the next quarter, coding works fine. Fortunatly we switched to a fully cloud based Azure DevOps some time last year. Also the team is itself performing admirably. My manager has directed us to develop a few contingency plans if/when sickness strikes our team.

A training I organized with Jon Reid I switched to fully remote last Sunday. It was supposed to take place in Amsterdam. We made our decision, only hours later the Dutch government announced severe measures in attempt to slow the spread of Corona. Jon was an excellent online host. He also informed me he arrived home safely back home in the US. I hope to do this again some day.

And then my wife. A teacher. She was forced to switch to working remote with her kids. From being hands-on to a laptop worker like me. She’s doing it. She makes me proud with her sheer determination to do what is best for her class within the boundaries of the current regulations.

I love working remote myself. But it has been a rough week. Everyone always has this virus on their mind. And not being able to do the regular things especially with and for my kids is hard. Especially when I see how much my oldest daughter needs her schoolwork as an outlet for the boundless energy she always has. Kids have lots of energy, but my oldest, she is ready to burst if she can not channel her energy on something that challenges her mind.

Beautiful things happen as well inspired by the social limitations we have to accept. Last Wednesday the first ever CocoaHeadsNL online meetup was a success. Over 90 people were able to forget about the current situation and enjoy an online CocoaHeadsNL meetup presented by Antoinne van der Lee. I think it went well. People really appreciated the livestream. Next month we probably can not do a live meetup yet, so if all goes well we will do another online edition.

It is unsure how long this social distancing will last. It sucks. But I believe in the reasoning behind it. I do not want to see the same thing happen anywhere like they are currently in Italy. I cried when I read the news about the choices health care workers have to make in northern Italy.

If anyone in my social circle needs some help, either CocoaHead, colleague, friend or neighbor… I hope they have the courage to reach out.

Take care of each other. We’ll see each other when this whole thing is behind us.

Categories
Random thoughts

CocoaHeadsNL

First of all, all opinions and information is. based on my view of things. It is based on my incomplete memories.

This week I want to write a bit about CocoaHeadsNL. It’s been a week of Corona/COVID19 news. AppDevCon has been postponed to September. And the CocoaHeadsNL meetup of this month has all of a sudden been converted to an online only event. Something CocoaHeadsNL has never done. On top of that, last week I had some late cancellations on my TDD workshop for iOS by Jon Reid.

Opinions vary. I don’t want to go too much into that. Let the experts provide the guidance we need. In the Netherlands that’s the National Institute for Public Health and the Environment.

But it did trigger me to look back on what CocoaHeadsNL has become. And I think it is doing a pretty good job in achieving its stated goals (Dutch) in the notary document that created the non-profit CocoaHeadsNL.

Please be aware. This whole write-up will most likely go all over the place. I did not write this with any preconceived writing plan.

How did CocoaHeadsNL get started?

Other people have told me various things. All I know for sure is that it was very informal and in a very small setting. To my knowledge the first gathering calling itself CocoaHeadsNL was on December 10, 2008. Somewhere in Amsterdam.

How did I get involved with CocoaHeadsNL?

Peter Robinett (employee nr. 2 at BUNQ, he kept calling it Project Clear) was running CocoaHeadsNL when I happened to start attending CocoaHeadsNL meetups. It was during my time at ING when I was working on their consumer iOS app. Sleeping in a hotel 3 nights a week was getting very old very fast, so that’s why I started looking for things to do in the evenings. One fateful evening Peter casually asked if anyone was willing to help him run these Meetups, cause his current project was just taking too much time. Me and very few others raised their hand. By luck of the draw Peter pointed at me and agreed to let me help him with organizing CocoaHeadsNL. This was somewhere at the start of 2013. Then somewhere in 2013 Peter broke the news. “I’m moving to New York. You will get full control of CocoaHeadsNL. Ask me anything for the next couple of months, but I will be fading to the background very soon.”

I was surprised, flattered, and a bit scared and overwhelmed. How to run this thing? First thing I did was make an inventory of all the channels and mediums CocoaHeadsNL was using. To be able to do this I needed to consolidate things. So I took a gamble and put CocoaHeadsNL on Meetup. And it worked, I finally got some grasp on who called themselves a CocoaHead and was interested in our meetups. I did my best, but then I put out a call for help during a Meetup. Organizing CocoaHeadsNL meetups is fun, but too much work for a single person. Bart and Niels stepped up.

Becoming a non-profit foundation

Things settled down and slowly CocoaHeadsNL started growing. Close to 200 people. Companies were willing to donate their office space and drinks and sometimes food. Sometimes we asked, could you sponsor us, to help offset some of the costs we are currently carrying ourselves? The answer was always “No.” And every time when we asked further it was always a case of not wanting to run the risk of getting the transaction tagged by the Dutch Tax services as a work agreement.

How to fix this? Easiest was to incorporate CocoaHeadsNL as a legal entity. After some discussion we agreed that a non-profit with a stature that protects our ideologic goal of bringing software developers into contact with each other and with potential employers in a fun, non intrusive and open way based on the agreement everyone comes together to openly share knowledge and information.

I was working at Xebia at the time. I pitched my idea, would Xebia be willing to fund the founding of the CocoaHeadsNL non-profit? They agreed. Not only to paying all legal fees but also to allow CocoaHeadsNL to remain an entity completely unattached to Xebia.

So at Monday Februari 2 2015 Bart, Niels and me appeared before Notary Cornelis van de Griend. A few cups of coffee and a few signatures later Stichting CocoaHeadsNL was incorporated as a non-profit foundation.

CocoaHeadsNL in 2020

Lots of things have happened. Niels left. Marco and Jolanda joined. We started recording videos. And thanks to Marco’s enthusiasm they have been getting better and better. We also organized our first conference as a non-profit. The third edition of Do iOS.

What’s it like to organize CocoaHeadNL

Due to the COVID19 concerns we will be running an experiment this month. Our first online Meetup. We have no idea if and how it will work. But that’s the fun thing with CocoaHeadsNL. There’s always something new.

Looking back, I think CocoaHeadsNL has been one of the things that on the surface doesn’t “give” that much. But let’s see it got me in touch with other people, other opinions. And that combined with me getting kids allowed me to think broader. It allowed me to consider what is important for me. Due to my experience with running CocoaHeadsNL I started freelancing on the side. Have organized several conferences. Created an open training with the help of Jon Reid (maybe more will follow).

But best of all is the great many people I meet and see on a regular basis to have fun with, bounce ideas of and just have a good time. Also Bart and me have become very well attuned to each other. I provide the crazy ideas. But Bart is always there to talk sense in to me, point out the errors in my business thinking and sometimes he just tells me I did something stupid.

CocoaHeadsNL has made me a better person. And I hope my tenure as its chairperson can last a long time. See you at the next Meetup.

I was able to squeeze 9 links into this thing. :)

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)
    }
}

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.