Create a key chain for Apple's VPN

While trying to get a VPN connection pro grammatically up and running using swift (check my VPN blog post), I hit a couple of obstacles. One big obstacle was how to pass VPN password and shared key to NEVPNProtocol class. Passing plain strings didn't work.

It then turned out that I need to pass a reference to a key chain entry that I saved before. The problem was that Apple's key chains seem quite difficult to handle manually. Luckily there are a couple of swift and Objective-C libraries out there that handle Key Chains, the most famous one is KeychainAccess. I thought all my problems were solved, but alas that was just the begining. I saved the password and passed a data reference to the NEVPNIPSec class but I couldn't establish the VPN connection. The error was that authentication failed.

Anyways after some search and retires, I could finally solve the problem using an Objective-C snippet I found online (sorry I lost the original URL). And here's the swift equivalent snippet.

import Foundation

import UIKit  
import Security

// Identifiers
let serviceIdentifier = "MySerivice"  
let userAccount = "authenticatedUser"  
let accessGroup = "MySerivice"

// Arguments for the keychain queries
var kSecAttrAccessGroupSwift = NSString(format: kSecClass)

let kSecClassValue = kSecClass as CFString  
let kSecAttrAccountValue = kSecAttrAccount as CFString  
let kSecValueDataValue = kSecValueData as CFString  
let kSecClassGenericPasswordValue = kSecClassGenericPassword as CFString  
let kSecAttrServiceValue = kSecAttrService as CFString  
let kSecMatchLimitValue = kSecMatchLimit as CFString  
let kSecReturnDataValue = kSecReturnData as CFString  
let kSecMatchLimitOneValue = kSecMatchLimitOne as CFString  
let kSecAttrGenericValue = kSecAttrGeneric as CFString  
let kSecAttrAccessibleValue = kSecAttrAccessible as CFString

class KeychainService: NSObject {  
    func save(key:String, value:String) {
        let keyData: Data = key.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)!
        let valueData: Data = value.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)!

        let keychainQuery = NSMutableDictionary();
        keychainQuery[kSecClassValue as! NSCopying] = kSecClassGenericPasswordValue
        keychainQuery[kSecAttrGenericValue as! NSCopying] = keyData
        keychainQuery[kSecAttrAccountValue as! NSCopying] = keyData
        keychainQuery[kSecAttrServiceValue as! NSCopying] = "VPN"
        keychainQuery[kSecAttrAccessibleValue as! NSCopying] = kSecAttrAccessibleAlwaysThisDeviceOnly
        keychainQuery[kSecValueData as! NSCopying] = valueData;
        // Delete any existing items
        SecItemDelete(keychainQuery as CFDictionary)
        SecItemAdd(keychainQuery as CFDictionary, nil)
    }

    func load(key: String)->Data {

        let keyData: Data = key.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)!
        let keychainQuery = NSMutableDictionary();
        keychainQuery[kSecClassValue as! NSCopying] = kSecClassGenericPasswordValue
        keychainQuery[kSecAttrGenericValue as! NSCopying] = keyData
        keychainQuery[kSecAttrAccountValue as! NSCopying] = keyData
        keychainQuery[kSecAttrServiceValue as! NSCopying] = "VPN"
        keychainQuery[kSecAttrAccessibleValue as! NSCopying] = kSecAttrAccessibleAlwaysThisDeviceOnly
        keychainQuery[kSecMatchLimit] = kSecMatchLimitOne
        keychainQuery[kSecReturnPersistentRef] = kCFBooleanTrue

        var result: AnyObject?
        let status = withUnsafeMutablePointer(to: &result) { SecItemCopyMatching(keychainQuery, UnsafeMutablePointer($0)) }


        if status == errSecSuccess {
            if let data = result as! NSData? {
                if let value = NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) {
                }
                return data as Data;
            }
        }
        return "".data(using: .utf8)!;
    }
}

Using this class, I was able to save password strings into the keychain and again pass them to NEVPNProtocol class as data references.

This code is quite similar to the KeychainAccess library, but it sets some more keys/attributes, that are currently not possible to set explicitly from outside, for example kSecAttrServiceValue.

comments powered by Disqus