NWHotSpotConfiguration not providing a helpful error message

I have the following code that is attempting to set up Hotspot 2.0 using an EAP-TLS configuration. I am importing a pk12 file and using those certificates.
I have tried all manner of permutations for the configuration, and have narrowed down all the errors I was getting and now I am just getting a generic:

Error: invalid EAP settings.

I have tried adding the identity separately and either get an entitlements issue which I can't figure out why since I have added the required network extension sharing groups, or a duplicate item error, meaning it was already correctly added.

The certificate and configuration are correctly working through an Android app already.

   static let accessGroup: String? = {
        guard let prefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as? String else {
            print("Could not load group")
            return nil
        }
        return "\(prefix)com.apple.networkextensionsharing"
    }()

 static func setupHotspot(data: CertificateData) {
                
        
        let h20 = NEHotspotHS20Settings(domainName: data.realm, roamingEnabled: false)
        h20.naiRealmNames = [data.realm]

        var result: CFArray?
        let options: [CFString: Any] = [
            kSecImportExportPassphrase: "**********",
            kSecAttrLabel: "ident:\(data.user)",
            kSecAttrAccessGroup: accessGroup!,
            kSecReturnPersistentRef: true
        ]
        
        let status = SecPKCS12Import(data.p12 as CFData, options as CFDictionary, &result)
        
        guard status == errSecSuccess,
                let importResult = result as? [[String: Any]],
                let resultDict = importResult.first else {
            print("P12 Import failed: \(status)")
            return
        }
        
        let identity = resultDict[kSecImportItemIdentity as String] as! SecIdentity
        
 
        let eap = NEHotspotEAPSettings()
   
        eap.supportedEAPTypes = [NEHotspotEAPSettings.EAPType.EAPTLS.rawValue as NSNumber]

        eap.isTLSClientCertificateRequired = true
        eap.trustedServerNames = [ data.realm ]
        eap.outerIdentity = "anonymous"

        guard eap.setIdentity( identity ) else {
            print("setIdentity failed")
            return
        }


        let configuration = NEHotspotConfiguration(hs20Settings: h20, eapSettings: eap)

        NEHotspotConfigurationManager.shared.apply(configuration) { error in
            if let error = error {
                print("Error: \(error.localizedDescription)")
            } else {
                print("Success")
            }
        }
    }
Answered by DTS Engineer in 882747022

Are you sure that your accessGroup calculation is working as expected? AppIdentifierPrefix isn’t a standard Info.plist key, so for this to work you must be adding it to your Info.plist yourself.

Also, is your app using a unique App ID prefix? Or your Team ID as the App ID prefix? The docs say to use the Team ID, but I think that’s a case of them just assuming the modern world where the App ID prefix is always the Team ID. If you’re using is using a unique App ID prefix I’m not entirely sure what’ll happen, so lemme know in that case.

I have tried adding the identity separately

You’ll definitely need to do that. The keys you’re passing to SecPKCS12Import, kSecAttrAccessGroup and kSecReturnPersistentRef, don’t do what you’re hoping they’ll do (I suspect they’re just being ignored).

My advice is that you call SecPKCS12Import and then call SecItemAdd.

If that process is failing, it’s likely that you’re falling in to one of the many pitfalls of the SecItem API. I talk about this in depth in:

Pasted in at the end of this reply you’ll find two snippets of code that I used to test this stuff:

  • importIdentityAction() shows how to import the identity, add it to the keychain, and set it on NEHotspotEAPSettings.
  • resetAction() shows how to wipe the keychain, so you can start from scratch between tests. This is useful when wrangling problems like this, because it avoids the complexities of handling keychain item uniqueness in your test app. In a real app, you do have to wrangle with that complexity, which is not helped by the fact that digital identities aren’t real (see the above posts for an explanation of that comment).

Oh, and these snippets rely on the identityNamed(…) routine, which you can find on this thread.

now I am just getting a generic

Right. My experience is that this error is usually accompanied by a system log entry with more details. Specifically, look for log entries with com.apple.networkextension as the subsystem. So, once you’ve got your keychain stuff sorted out, if you’re still getting this error, monitor the system log during the failure and see what you get.

If you’re not familiar with the system log, have a read of Your Friend the System Log.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


func importIdentityAction() {
    do {
        print("will set identity")

        let identity = Bundle.main.identityNamed("Benjy", password: "test")!

        try secCall {
            SecItemAdd([
                kSecValueRef: identity,
                kSecAttrAccessGroup: "SKMME9E2Y8.com.apple.networkextensionsharing",
            ] as NSDictionary, nil)
        }
        
        let eap = NEHotspotEAPSettings()
        let success = eap.setIdentity(identity)
        if success {
            print("did set identity")
        } else {
            print("did not set identity")
        }
    } catch {
        print("did not set identity, error: \(error)")
    }
}

func resetAction() {
    print("will reset")
    _ = SecItemDelete([kSecClass: kSecClassCertificate] as NSDictionary)
    _ = SecItemDelete([kSecClass: kSecClassKey] as NSDictionary)
    print("did reset")
}
Accepted Answer

Are you sure that your accessGroup calculation is working as expected? AppIdentifierPrefix isn’t a standard Info.plist key, so for this to work you must be adding it to your Info.plist yourself.

Also, is your app using a unique App ID prefix? Or your Team ID as the App ID prefix? The docs say to use the Team ID, but I think that’s a case of them just assuming the modern world where the App ID prefix is always the Team ID. If you’re using is using a unique App ID prefix I’m not entirely sure what’ll happen, so lemme know in that case.

I have tried adding the identity separately

You’ll definitely need to do that. The keys you’re passing to SecPKCS12Import, kSecAttrAccessGroup and kSecReturnPersistentRef, don’t do what you’re hoping they’ll do (I suspect they’re just being ignored).

My advice is that you call SecPKCS12Import and then call SecItemAdd.

If that process is failing, it’s likely that you’re falling in to one of the many pitfalls of the SecItem API. I talk about this in depth in:

Pasted in at the end of this reply you’ll find two snippets of code that I used to test this stuff:

  • importIdentityAction() shows how to import the identity, add it to the keychain, and set it on NEHotspotEAPSettings.
  • resetAction() shows how to wipe the keychain, so you can start from scratch between tests. This is useful when wrangling problems like this, because it avoids the complexities of handling keychain item uniqueness in your test app. In a real app, you do have to wrangle with that complexity, which is not helped by the fact that digital identities aren’t real (see the above posts for an explanation of that comment).

Oh, and these snippets rely on the identityNamed(…) routine, which you can find on this thread.

now I am just getting a generic

Right. My experience is that this error is usually accompanied by a system log entry with more details. Specifically, look for log entries with com.apple.networkextension as the subsystem. So, once you’ve got your keychain stuff sorted out, if you’re still getting this error, monitor the system log during the failure and see what you get.

If you’re not familiar with the system log, have a read of Your Friend the System Log.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


func importIdentityAction() {
    do {
        print("will set identity")

        let identity = Bundle.main.identityNamed("Benjy", password: "test")!

        try secCall {
            SecItemAdd([
                kSecValueRef: identity,
                kSecAttrAccessGroup: "SKMME9E2Y8.com.apple.networkextensionsharing",
            ] as NSDictionary, nil)
        }
        
        let eap = NEHotspotEAPSettings()
        let success = eap.setIdentity(identity)
        if success {
            print("did set identity")
        } else {
            print("did not set identity")
        }
    } catch {
        print("did not set identity, error: \(error)")
    }
}

func resetAction() {
    print("will reset")
    _ = SecItemDelete([kSecClass: kSecClassCertificate] as NSDictionary)
    _ = SecItemDelete([kSecClass: kSecClassKey] as NSDictionary)
    print("did reset")
}

Thank you so much Quinn,

The documentation about uniqueness in the keychain, and the reset keychain function were extremely helpful in debugging my issues. I will lay them out here for other users who may end up in my prediciment.

The import of the pk12 file was not putting keys in the keychain, the identity was leftover from previous testing, and was causing me more issues than I would like to admit. The posts you linked should be official documentation because they are more helpful than anything else I could find on my own.

Once I was sure that I was adding the identity correctly, I found I was getting rejected for missing entitlements. I triple checked that everything was correct ( my plist info file was correctly adding my AppIdentifierPrefix ) I found something very frustrating in the official apple developer documenation: https://developer.apple.com/documentation/networkextension/nehotspoteapsettings/setidentity(_:)#Discussion

$(Team​Identifier​Prefix)com​.apple​.networkextensionsharing.

The entitlement group in the documentation has a unicode zero width space between apple and .networkextensionsharing. so when I copied it into my entitlements, it would not work.

Once I fixed these two things I was able to add everything needed to the keychain and get a working EAP-TLS setup.

Thanks again.

Glad to hear you’re making progress.

The posts you linked should be official documentation

If you’d like to request that officially, please file a bug against the docs. It’d be helpful if you called out the specific bits that you found helpful. And if you do file this bug, please post your bug number, just for the record.

The entitlement group in the documentation has a unicode zero width space

Oi vey! I filed my own bug about that [1].

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Actually, three bugs:

  • One against the docs (r. 174003674)
  • Another against the entitlements complier (r. 174003869)
  • And a third one against the Swift complier (r. 174003886)

I feel very accomplished (-:

NWHotSpotConfiguration not providing a helpful error message
 
 
Q