ios - 在 Swift 中使用网络扩展框架 iOS 8 的 VPN 连接不起作用

标签 ios swift

我一直在尝试使用 iOS 8 网络扩展框架在用户按下 UIButton 时设置 VPN 连接。我使用了以下教程:http://ramezanpour.net/post/2014/08/03/configure-and-manage-vpn-connections-programmatically-in-ios-8/ .我也看了这个帖子Can't setup VPN connection using Network Extension Framework iOS 8 in Swift ,这与我得到的行为相同。当我运行该应用程序时,我会在安装配置文件时提示输入 vpn 密码和共享 key ,即使它们已在代码中设置了所有其他必需值。如果我在安装配置文件时输入这些详细信息,它仍然不起作用。尝试使用该应用程序进行连接时,会出现“没有 sharedSecret”错误。在引用的帖子中,通过重写在 OBJ-C 中保存和访问钥匙串(keychain)数据的代码,问题显然得到了解决。我想让它快速工作或理解为什么它不能快速工作。

这是连接的代码

let manager = NEVPNManager.sharedManager()
@IBAction func connectToVpn(sender: AnyObject) {
  println("in call vpn")
           manager.loadFromPreferencesWithCompletionHandler { (error) -> Void in
             if((error) != nil) {
                 println("VPN Preferences error: 1")
               }
              else {
                    var p = NEVPNProtocolIPSec()
                    p.username = "billy"
                    p.serverAddress = "xxx.xxx.xxx.xxx"
                    p.passwordReference = self.loadkeychain("vpnpassword")
                    println(p.passwordReference)
                    p.authenticationMethod = NEVPNIKEAuthenticationMethod.SharedSecret
                    p.sharedSecretReference = self.loadkeychain("sharedSecret")
                    println(p.sharedSecretReference)
                    p.localIdentifier = "vpn"
                    p.remoteIdentifier = "vpn"
                    p.disconnectOnSleep = false
                    println("everything is set")
                    self.manager.`protocol` = p
                    self.manager.onDemandEnabled = true
                    self.manager.localizedDescription = "VPN"
                    self.manager.saveToPreferencesWithCompletionHandler({ (error) -> Void in
                        if((error) != nil) {
                            println("VPN Preferences error: 2")
                            println(error)
                        }
                        else {
                            var startError: NSError?
                            self.manager.connection.startVPNTunnelAndReturnError(&startError)
                            if((startError) != nil) {
                                println("VPN Preferences error: 3")
                                println(startError)}
                            else {
                                println("Start VPN")
                            }
                        }
                    })
                }
            }
        }

这是从钥匙串(keychain)中保存和检索的代码

func savekeychain(key: String, value: String) -> Bool {
         let valueData = value.dataUsingEncoding(NSUTF8StringEncoding,
            allowLossyConversion: false)
           let service = NSBundle.mainBundle().bundleIdentifier!
                let secItem = [
                      kSecClass as! String :
                      kSecClassGenericPassword as! String,
                      kSecAttrService as! String : service,
                      kSecAttrAccount as! String : key,
                       kSecValueData as! String : valueData!,
        ]
           var result: Unmanaged<AnyObject>? = nil
            let status = Int(SecItemAdd(secItem, &result))
                switch status{
                  case Int(errSecSuccess):
                  println("Successfully stored the value")
                   case Int(errSecDuplicateItem):
                   println("This item is already saved. Cannot duplicate it")
        default:
            println("An error occurred with code \(status)")
        }
                return true
    }

    func loadkeychain(keyToSearchFor: String) -> NSData 
        let service = NSBundle.mainBundle().bundleIdentifier!
         let query = [
          kSecClass as! String :
           kSecClassGenericPassword as! String,
            kSecAttrService as! String : service,
            kSecAttrAccount as! String : keyToSearchFor,
            kSecReturnData as! String : kCFBooleanTrue,
        ]
       var data: NSData!
        var returnedData: Unmanaged<AnyObject>? = nil
        let results = Int(SecItemCopyMatching(query, &returnedData))
         if results == Int(errSecSuccess){
              data = returnedData!.takeRetainedValue() as! NSData
               let value = NSString(data: data, encoding: NSUTF8StringEncoding)
               println("Value = \(value)")
                println("DATA = \(data)")
                    } else {
                  println("Error happened with code: \(results)")
        }
                     return data
    }

按照引用帖子的建议,最终将我从钥匙串(keychain)保存和检索数据的函数更改为 OBJ-C 方法,这确实解决了问题。一些测试表明 swift 和 OBJ-C 方法都返回相同的值,所以我不确定为什么 swift 方法会导致所述行为。我注意到的另一件事是,将值保存到钥匙串(keychain)似乎有点不稳定,如果您删除一个 key 然后用不同的值重新添加它似乎不起作用,需要将钥匙串(keychain)重置为默认值。我仍然想弄清楚为什么 swift 方法似乎无法正常工作。

最佳答案

我知道我来晚了,但它可能会帮助我们在 secItemCopyMatchig 中需要共享 secret 引用的人,您需要添加 kSecReturnPersistentRef 并设置为 true。 下面的 block 可能会帮助你。

enum VPNKeychain {

    /// Returns a persistent reference for a generic password keychain item, adding it to
    /// (or updating it in) the keychain if necessary.
    ///
    /// This delegates the work to two helper routines depending on whether the item already
    /// exists in the keychain or not.
    ///
    /// - Parameters:
    ///   - service: The service name for the item.
    ///   - account: The account for the item.
    ///   - password: The desired password.
    /// - Returns: A persistent reference to the item.
    /// - Throws: Any error returned by the Security framework.

    static func persistentReferenceFor(service: String, account: String, password: Data) throws -> Data {
        var copyResult: CFTypeRef? = nil
        let err = SecItemCopyMatching([
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecReturnPersistentRef: true,
            kSecReturnData: true
            ] as NSDictionary, &copyResult)
        switch err {
        case errSecSuccess:
            return try self.persistentReferenceByUpdating(copyResult: copyResult!, service: service, account: account, password: password)
        case errSecItemNotFound:
            return try self.persistentReferenceByAdding(service: service, account:account, password: password)
        default:
            try throwOSStatus(err)
            // `throwOSStatus(_:)` only returns in the `errSecSuccess` case.  We know we're
            // not in that case but the compiler can't figure that out, alas.
            fatalError()
        }
    }

    /// Returns a persistent reference for a generic password keychain item by updating it
    /// in the keychain if necessary.
    ///
    /// - Parameters:
    ///   - copyResult: The result from the `SecItemCopyMatching` done by `persistentReferenceFor(service:account:password:)`.
    ///   - service: The service name for the item.
    ///   - account: The account for the item.
    ///   - password: The desired password.
    /// - Returns: A persistent reference to the item.
    /// - Throws: Any error returned by the Security framework.

    private static func persistentReferenceByUpdating(copyResult: CFTypeRef, service: String, account: String, password: Data) throws -> Data {
        let copyResult = copyResult as! [String:Any]
        let persistentRef = copyResult[kSecValuePersistentRef as String] as! NSData as Data
        let currentPassword = copyResult[kSecValueData as String] as! NSData as Data
        if password != currentPassword {
            let err = SecItemUpdate([
                kSecClass: kSecClassGenericPassword,
                kSecAttrService: service,
                kSecAttrAccount: account,
                ] as NSDictionary, [
                    kSecValueData: password
                    ] as NSDictionary)
            try throwOSStatus(err)
        }
        return persistentRef
    }

    /// Returns a persistent reference for a generic password keychain item by adding it to
    /// the keychain.
    ///
    /// - Parameters:
    ///   - service: The service name for the item.
    ///   - account: The account for the item.
    ///   - password: The desired password.
    /// - Returns: A persistent reference to the item.
    /// - Throws: Any error returned by the Security framework.

    private static func persistentReferenceByAdding(service: String, account: String, password: Data) throws -> Data {
        var addResult: CFTypeRef? = nil
        let err = SecItemAdd([
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecValueData: password,
            kSecReturnPersistentRef: true,
            ] as NSDictionary, &addResult)
        try throwOSStatus(err)
        return addResult! as! NSData as Data
    }

    /// Throws an error if a Security framework call has failed.
    ///
    /// - Parameter err: The error to check.

    private static func throwOSStatus(_ err: OSStatus) throws {
        guard err == errSecSuccess else {
            throw NSError(domain: NSOSStatusErrorDomain, code: Int(err), userInfo: nil)
        }
    }
}

关于ios - 在 Swift 中使用网络扩展框架 iOS 8 的 VPN 连接不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30986087/

相关文章:

objective-c - UITableviewcell 加载数据问题

swift - 为什么 Swift 接受应该返回元组的代码,但却返回字符串?

ios - 在 Swift 中通过 TCP 发送消息(NULL 终止)

ios - 在 iOS 中实现自动完成

javascript - 将鼠标移动改为触摸控制

iphone - 如何覆盖@synthesized getter?

ios - 如何使用 HP SCA 扫描我的 iOS 项目并获取报告

ios - 如何从 UIImagePickerController 获取 1080p 视频?

ios - Swift - 实例成员不能在类型 ViewController 上使用

ios - 什么时候调用协议(protocol)函数?