swift - 以编程方式连接到 VPN 不断要求提供系统钥匙串(keychain)凭据

标签 swift macos certificate vpn nevpnmanager

我的代码使用 NEVPNManager 和证书(在 MacOS 上)连接到 VPN,该代码运行良好,但每当我尝试连接时 (targetManager.connection.startVPNTunnel())系统提示输入系统钥匙串(keychain)凭据。

macos prompt for system keychain credentials

是否可以在第一次批准后让此警报消失?

代码:

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)
    }
}

}

最佳答案

解决方法是不使用 identityDataidentityDataPassword ,而是自行将身份导入到用户的钥匙串(keychain)中(使用 SecItemImport),然后通过通过 identityReference 属性对 NEVPNManager 身份进行持久引用。
这是一个工作示例:

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, &copyResult)
    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)
}
}

关于swift - 以编程方式连接到 VPN 不断要求提供系统钥匙串(keychain)凭据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48807043/

相关文章:

ios - 尝试在没有 ViewController 的情况下使用 CollectionViewController 时出错

c++ - 使用 Clang 的 "undefined" sanitizer 时 undefined symbol

clickonce - 代码签名需要什么证书?

ios - 无法将主要的 swift 类导入测试目标?

ios - 使用 HTTPCookieStorage.shared 在 URLSessionConfiguration.background 中授权失败(cookes 未发送)

swift - 移至回收站时的 URL 书签数据

macos - 从 cmd 行登录 Docker 会产生 i/o 超时

c - 试验 SSL 连接

security - 数字证书在用于保护网站(使用 SSL)时如何工作?

ios - 如何比较数组与嵌套数组并打印匹配对,而不是匹配的值列表? (代码有助于说明)