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"