My code connects to VPN using NEVPNManager
and certificate (on MacOS), the code works good but whenever I try to connect (targetManager.connection.startVPNTunnel()
) the system prompt for system keychain credentials.
Is there to make this alert go away after the first approval?
Code:
func initVPNTunnelProviderManager(vpnConfig: Vpn, _ connect: Bool = false) {
let url = URL(string: vpnConfig.certUrl!)
do {
let certData = try Data(contentsOf: url!)
let targetManager: NEVPNManager = NEVPNManager.shared()
targetManager.loadFromPreferences(completionHandler: { (error:Error?) in
if let error = error {
print(error)
}
switch targetManager.connection.status {
case NEVPNStatus.connected:
targetManager.connection.stopVPNTunnel()
break
case NEVPNStatus.disconnected:
let ip = vpnConfig.serverUrl
let providerProtocol = NEVPNProtocolIKEv2()
providerProtocol.authenticationMethod = .certificate
providerProtocol.serverAddress = ip
providerProtocol.remoteIdentifier = ip
providerProtocol.localIdentifier = "myIdentifier"
providerProtocol.useExtendedAuthentication = false
providerProtocol.ikeSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES128GCM
providerProtocol.ikeSecurityAssociationParameters.diffieHellmanGroup = .group19
providerProtocol.ikeSecurityAssociationParameters.integrityAlgorithm = .SHA512
providerProtocol.ikeSecurityAssociationParameters.lifetimeMinutes = 20
providerProtocol.childSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES128GCM
providerProtocol.childSecurityAssociationParameters.diffieHellmanGroup = .group19
providerProtocol.childSecurityAssociationParameters.integrityAlgorithm = .SHA512
providerProtocol.childSecurityAssociationParameters.lifetimeMinutes = 20
providerProtocol.deadPeerDetectionRate = .medium
providerProtocol.disableRedirect = true
providerProtocol.disableMOBIKE = false
providerProtocol.enableRevocationCheck = false
providerProtocol.enablePFS = true
providerProtocol.useConfigurationAttributeInternalIPSubnet = false
providerProtocol.serverCertificateCommonName = ip
providerProtocol.serverCertificateIssuerCommonName = ip
providerProtocol.disconnectOnSleep = true
providerProtocol.identityDataPassword = vpnConfig.certPassword
providerProtocol.certificateType = .ECDSA256
providerProtocol.identityData = certData
targetManager.protocolConfiguration = providerProtocol
targetManager.localizedDescription = vpnConfig.name
targetManager.isEnabled = true
targetManager.isOnDemandEnabled = false
targetManager.saveToPreferences(completionHandler: { (error:Error?) in
if let error = error {
print(error)
} else {
print("Save successfully")
if connect {
do {
try targetManager.connection.startVPNTunnel()
} catch {
print("Failed to connect")
}
}
}
})
break
default:
print("connection status not handled: \(targetManager.connection.status.rawValue)")
}
})
} catch {
print(error.localizedDescription)
}
}
}
The workaround is to not use identityData
and identityDataPassword
but instead import the identity into the user’s keychain yourself (using SecItemImport
) and then pass a persistent reference to the identity to NEVPNManager
via the identityReference
property.
Here's a working sample:
private func identityReference(for pkcs12Data: Data, password: String) -> Data {
var importResult: CFArray? = nil
let err = SecPKCS12Import(pkcs12Data as NSData, [
kSecImportExportPassphrase: password
] as NSDictionary, &importResult)
guard err == errSecSuccess else { fatalError() }
let importArray = importResult! as! [[String:Any]]
let identity = importArray[0][kSecImportItemIdentity as String]! as! SecIdentity
var copyResult: CFTypeRef? = nil
let err2 = SecItemCopyMatching([
kSecValueRef: identity,
kSecReturnPersistentRef: true
] as NSDictionary, ©Result)
guard err2 == errSecSuccess else { fatalError() }
return copyResult! as! Data
}
func initVPNTunnelProviderManager(vpnConfig: Vpn, _ connect: Bool = false) {
let url = URL(string: vpnConfig.certUrl!)
do {
let certData = try Data(contentsOf: url!)
let targetManager: NEVPNManager = NEVPNManager.shared()
targetManager.loadFromPreferences(completionHandler: { (error:Error?) in
if let error = error {
print(error)
}
switch targetManager.connection.status {
case NEVPNStatus.connected:
targetManager.connection.stopVPNTunnel()
break
case NEVPNStatus.disconnected:
let ip = vpnConfig.serverUrl
let providerProtocol = NEVPNProtocolIKEv2()
providerProtocol.authenticationMethod = .certificate
providerProtocol.serverAddress = ip
providerProtocol.remoteIdentifier = ip
providerProtocol.localIdentifier = "myIdentifier"
providerProtocol.useExtendedAuthentication = false
providerProtocol.ikeSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES128GCM
providerProtocol.ikeSecurityAssociationParameters.diffieHellmanGroup = .group19
providerProtocol.ikeSecurityAssociationParameters.integrityAlgorithm = .SHA512
providerProtocol.ikeSecurityAssociationParameters.lifetimeMinutes = 20
providerProtocol.childSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES128GCM
providerProtocol.childSecurityAssociationParameters.diffieHellmanGroup = .group19
providerProtocol.childSecurityAssociationParameters.integrityAlgorithm = .SHA512
providerProtocol.childSecurityAssociationParameters.lifetimeMinutes = 20
providerProtocol.deadPeerDetectionRate = .medium
providerProtocol.disableRedirect = true
providerProtocol.disableMOBIKE = false
providerProtocol.enableRevocationCheck = false
providerProtocol.enablePFS = true
providerProtocol.useConfigurationAttributeInternalIPSubnet = false
providerProtocol.serverCertificateCommonName = ip
providerProtocol.serverCertificateIssuerCommonName = ip
providerProtocol.disconnectOnSleep = true
providerProtocol.identityReference = self.identityReference(for: certData, password: vpnConfig.certPassword!)
providerProtocol.certificateType = .ECDSA256
targetManager.protocolConfiguration = providerProtocol
targetManager.localizedDescription = vpnConfig.name
targetManager.isEnabled = true
targetManager.isOnDemandEnabled = false
targetManager.saveToPreferences(completionHandler: { (error:Error?) in
if let error = error {
print(error)
} else {
print("Save successfully")
if connect {
do {
try targetManager.connection.startVPNTunnel()
} catch {
print("Failed to connect")
}
}
}
})
break
default:
print("connection status not handled: \(targetManager.connection.status.rawValue)")
}
})
} catch {
print(error.localizedDescription)
}
}