How to get an identity for NEPacketTunnelProvider Networkextension from com.apple.managed.vpn.shared keychain access group?

I am working on an Networkextension using NEPacketTunnelProvider. I am using a configuration profile with com.apple.vpn.managed payload. Furthermore, I use ClientCertificate authentication with an com.apple.security.pkcs12 payload. According to NETunnelProviderManager documentation https://developer.apple.com/documentation/networkextension/netunnelprovidermanager#1661706 it should be possible for my extension to retrieve this identity using the com.apple.managed.vpn.shared keychain access group.


If I query the Keychain for the identity, I always get error code `-25300`. According to https://www.osstatus.com/search/results?platform=all&framework=all&search=-25300 this means: "The item cannot be found."


Code 1:

I try to use the persistent reference provided by the protocolConfiguration to retrieve the identity.


class PacketTunnelProvider: NEPacketTunnelProvider {
        
        override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
            
            NSLog("vpn-service startTunnel")
            
            let pc = self.protocolConfiguration
            NSLog("vpn-service protocolConfiguration " + pc.debugDescription)
            if let identity = pc.identityReference {
                let persistentRef = identity as NSData
                NSLog("vpn-service persistentRef "  + persistentRef.description)
                var copyResult: AnyObject?
                let copyErr = SecItemCopyMatching([
                    kSecValuePersistentRef as String: persistentRef,
                    kSecReturnData as String: true
                    ] as CFDictionary, ©Result)
                NSLog("vpn-service getCert copyErr "  + copyErr.description)
            }
        }
    }
  


Output is:


Jul 27 10:07:53 Tims-iPhone vpn(libswiftFoundation.dylib)[4994] : vpn-service startTunnel
    Jul 27 10:07:53 Tims-iPhone vpn(libswiftFoundation.dylib)[4994] : vpn-service protocolConfiguration
        type = plugin
        identifier = 3B39941E-AF39-45CE-B869-68AF392FBCA0
        serverAddress = DEFAULT
        password = {
            domain = user
            accessGroup = com.apple.managed.vpn.shared
        }
        identity = {
            identifier = 
            persistentReference = <69646e74 00000000 00000011>
            domain = user
        }
        identityDataImported = NO
        identityReference = <69646e74 00000000 00000011>
        proxySettings = {
            autoProxyDiscovery = NO
            autoProxyConfigurationEnabled = NO
            HTTPEnabled = NO
            HTTPSEnabled = NO
            FTPEnabled = NO
            SOCKSEnabled = NO
            RTSPEnabled = NO
            gopherEnabled = NO
            excludeSimpleHostnames = NO
                    usePassiveFTP = YES
        }
        disconnectOnSleep = NO
        disconnectOnIdle = NO
        disconnectOnIdleTimeout = 0
        disconnectOnWake = NO
        disconnectOnWakeTimeout = 0
        pluginType = 
        authenticationMethod = 1
    Jul 27 10:07:53 Tims-iPhone vpn(libswiftFoundation.dylib)[4994] : vpn-service persistentRef <69646e74 00000000 00000011>
    Jul 27 10:07:53 Tims-iPhone vpn(libswiftFoundation.dylib)[4994] : vpn-service getCert copyErr -25300


Code 2:

If I try to retrieve all identitys without the reference, I get `-25300` too.


let getQuery: [String: Any] = [
        kSecClass as String: kSecClassIdentity,
        kSecMatchLimit as String: kSecMatchLimitAll,
        kSecReturnAttributes as String: true,
    ]
  
    var item: CFTypeRef?
    let status = SecItemCopyMatching(getQuery as CFDictionary, &item)
    NSLog("vpn-service status: " + status.description)


I rechecked that the build result got the keychain access group:


codesign -d --ent :- build/Debug-iphoneos/agent.app/PlugIns/vpn.appex/


Result:


Executable=/Users/timb/projects/xcode/ios-client/build/Debug-iphoneos/agent.app/PlugIns/vpn.appex/vpn
    
    
    
    
    application-identifier
    
    com.apple.developer.networking.networkextension
    
    com.apple.developer.team-identifier
    
    get-task-allow
    
    keychain-access-groups
    
    .com.apple.managed.vpn.shared
    
    
   


How to get an identity for NEPacketTunnelProvider Networkextension from com.apple.managed.vpn.shared keychain access group?


Edit: According to https://forums.developer.apple.com/thread/67613 #9 I need a special entitlement to get com.apple.managed.vpn.shared keychain access group. I created a TSI to get this entitlement.

Replies

According to https://forums.developer.apple.com/thread/67613 #9 I need a special entitlement to get

com.apple.managed.vpn.shared
keychain access group.

That’s correct. You won’t make any progress with this until you have access to this special entitlement.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I got it working now, I will post some example code, I hope it will be useful for somebody 🙂


1: When you have the special entitlement, make sure your container app and network extension have the entitlement set. You can check with `codesign -d --entitlements :- <filename>`


It should look like this:

<key>keychain-access-groups</key>
<array>
     <string>com.apple.managed.vpn.shared</string>
</array>


I am using XCode 10 Beta and there is a problem when you use the GUI. It sets the value like

<string>$(AppIdentifierPrefix)com.apple.managed.vpn.shared</string>

You have to fix it with a texteditor.


2: Access the data from the keychain. I will post some example code to use the data as base64 encoded strings.



guard let identityReference = protocolConfiguration.identityReference else {
    throw ConfigError.errorIdentityNotFound
}
let query: [String : Any] = [
    kSecValuePersistentRef as String: identityReference as CFData,
    kSecReturnRef as String: true,
]
      
var copyResult: AnyObject?
SecItemCopyMatching(query as CFDictionary, ©Result)
      
guard let existingCopyResult = copyResult else {
    throw ConfigError.errorIdentityNotFound
}
let identity = existingCopyResult as! SecIdentity
      
var certificate: SecCertificate!
SecIdentityCopyCertificate(identity, &certificate)
      
let DERCertificate = SecCertificateCopyData(certificate) as NSData
let DERCertificateStringBase64Encoded = DERCertificate.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters)
      
let PEMCertificateString = "-----BEGIN CERTIFICATE-----\n\(DERCertificateStringBase64Encoded)\n-----END CERTIFICATE-----"
      
var privateKey: SecKey!
SecIdentityCopyPrivateKey(identity, &privateKey)
let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, nil) as! NSData
let privateKeyDataBase64Encoded = privateKeyData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters)
let privateKeyPKCS1String = "-----BEGIN RSA PRIVATE KEY-----\n\(privateKeyDataBase64Encoded)\n-----END RSA PRIVATE KEY-----"