Hello. I am trying to setup Virtual Private Network (VPN) based on IKEV2 protocol which communicate with a Libreswan backend server. When I create .mobileconfig using AppleConfigurator2 all works fine.
I checked out the documentation and found that "On iOS for
NEVPNProtocolIPSec
and NEVPNProtocolIKEv2
objects the identity should be set using the identityData
property. And identityData is the "The certificate and private key components of the tunneling protocol authentication credential, encoded in PKCS12 format.".
Here is my function to establish VPN Connection:
func setupVPNConnectio(withData pkcs12Data: Data) { let manager: NEVPNManager = NEVPNManager.shared() manager.loadFromPreferences(completionHandler: { error in if let e = error { print("error: " + e.localizedDescription) return } let protocolIKEv2 = NEVPNProtocolIKEv2() protocolIKEv2.authenticationMethod = .certificate protocolIKEv2.certificateType = .RSA protocolIKEv2.serverAddress = "serverAddress" protocolIKEv2.remoteIdentifier = "remoteId" protocolIKEv2.localIdentifier = "localId" protocolIKEv2.identityData = pkcs12Data protocolIKEv2.identityDataPassword = "password" protocolIKEv2.deadPeerDetectionRate = .medium protocolIKEv2.ikeSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES128 protocolIKEv2.ikeSecurityAssociationParameters.integrityAlgorithm = .SHA256 protocolIKEv2.ikeSecurityAssociationParameters.diffieHellmanGroup = .group14 protocolIKEv2.ikeSecurityAssociationParameters.lifetimeMinutes = 1440 protocolIKEv2.childSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES128 protocolIKEv2.childSecurityAssociationParameters.integrityAlgorithm = .SHA256 protocolIKEv2.childSecurityAssociationParameters.diffieHellmanGroup = .group14 protocolIKEv2.childSecurityAssociationParameters.lifetimeMinutes = 1440 manager.protocolConfiguration = protocolIKEv2 manager.isEnabled = true manager.saveToPreferences(completionHandler: { error in guard error == nil else { print("NEVPNManager.saveToPreferencesWithCompletionsHandler failed: \(error!.localizedDescription)") return } do { try manager.connection.startVPNTunnel(options: nil) } catch (let exception) { print(exception) } }) }) }
But when I am trying to connect nothing is happened (without any errors). I connected VPN loging profile and AppleConfigurator2 to check logs. Here is output from console.
pfkey received SA is NULL
Certificate evaluation error =
kSecTrustResultRecoverableTrustFailure
Certificate is not trusted
Certificate authentication data could not be verified
Failed to process IKE Auth packet
When I trying to convert pkcs12 data to SecIdentity and evaluate SecTrust - nothing changed.
func evaluateIdentity(pkcs12Data: Data, password: String) throws { var importResult: CFArray? = nil let err = SecPKCS12Import(pkcs12Data as NSData, [kSecImportExportPassphrase as String: password] as NSDictionary, &importResult) guard err == errSecSuccess else { throw NSError(domain: NSOSStatusErrorDomain, code: Int(err), userInfo: nil) } let identityDictionaries = importResult as! [[String:Any]] let certificates = identityDictionaries[0] [kSecImportItemCertChain as String] as! [SecCertificate] let identity = identityDictionaries[0][kSecImportItemIdentity as String] as! SecIdentity let trust = identityDictionaries[0][kSecImportItemTrust as String] as! SecTrust var status = SecTrustSetAnchorCertificates(trust, certificates as CFArray ) var resultType = SecTrustResultType.invalid print(status) status = SecTrustEvaluate(trust, &resultType) //Received status 4 }
I also tried to save identiy to the keychain, but again - nothing changed.
func saveIdentity(identity: SecIdentity) { var certificateRef: SecCertificate? = nil SecIdentityCopyCertificate(identity, &certificateRef) let subject = SecCertificateCopySubjectSummary(certificateRef!) let saveQuery = [ kSecClass as String : kSecClassIdentity, kSecValueRef as String : identity, kSecReturnRef as String : kCFBooleanTrue, kSecAttrAccessible as String : kSecAttrAccessibleAlways, kSecAttrLabel as String : subject! ] as [String : Any] var item: CFTypeRef? = nil var status = SecItemAdd(saveQuery as CFDictionary, &item) // Received status 0 }
What I am doing wrong? And is it possible to establish this type of VPN connection programmatically?
Thanks.
Your profile confirmed what I suspected based on the following snippet from your original post:
Certificate evaluation error = kSecTrustResultRecoverableTrustFailure Certificate is not trusted
The profile contains a custom root certificate (a payload of type
com.apple.security.root
). Presumably your VPN server has a certificate issued by that custom root certificate. That works when you install the profile (your custom root certificate becomes trusted by the system as a whole) but is problematic for NEVPNManager because:
iOS has no API to install a trusted root certificate globally
NEVPNManager has no way to override IKEv2 server trust evaluation such that it’ll trust your custom root certificate in this context
If you want to continue down the NEVPNManager path you will have to get a trusted CA to issue you a certificate for your VPN server.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"