iOS通过NEHotspotConfiguration实现EAP证书认证连接失败求助
问题概述
通过NEHotspotConfiguration连接EAP-TLS加密WiFi时,账号密码认证可正常成功,但使用客户端证书+CA证书认证时失败,设备提示「无法加入网络」,调试日志输出以下错误:
NEHotspotEAPSettings failed to find persistent reference for identity. status = -25300
NEHotspotEAPSettings failed to find persistent reference for trusted server certificate. status = -25300
NEHotspotEAPSettings found nil persistent reference for identity
测试环境:iPhone 12 Pro Max(iOS 16.2),Cisco控制器。
代码对比
认证成功的账号密码代码
NEHotspotEAPSettings *eapSettings = [[NEHotspotEAPSettings alloc] init]; eapSettings.username = @"username"; eapSettings.password = @"password"; eapSettings.supportedEAPTypes = @[@(NEHotspotConfigurationEAPTypeEAPPEAP)]; eapSettings.outerIdentity = @""; eapSettings.trustedServerNames = @["xxx.xxx.com"]; NEHotspotConfiguration *hotspotConfig = [[NEHotspotConfiguration alloc] initWithSSID:@"ssidName" eapSettings:eapSettings]; [[NEHotspotConfigurationManager sharedManager] applyConfiguration:hotspotConfig completionHandler:^(NSError * _Nullable error) { NSLog(@"success"); }];
认证失败的证书认证代码
NSString *caCertificatePath = [[NSBundle mainBundle] pathForResource:@"caCertificate" ofType:@"cer"]; NSData *caCertificateData = [[NSData alloc] initWithContentsOfFile:caCertificatePath]; SecCertificateRef caCertificateRef = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)caCertificateData); SecIdentityRef userIdentity = nil; NSString *p12Path = [[NSBundle mainBundle] pathForResource:@"userCertificate" ofType:@"p12"]; NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:p12Path]; CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data; CFStringRef password = CFSTR("password"); const void *keys[] = { kSecImportExportPassphrase }; const void *values[] = { password }; CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); CFArrayRef items = NULL; OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items); CFRelease(options); CFRelease(password); if (securityError == errSecSuccess) { NSLog(@"Success opening p12 certificate. Items: %ld", CFArrayGetCount(items)); CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0); userIdentity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity); NEHotspotEAPSettings *eapSettings = [[NEHotspotEAPSettings alloc] init]; eapSettings.tlsClientCertificateRequired = YES; eapSettings.trustedServerNames = @["xxx",@"xxx.xxx.com"]; eapSettings.supportedEAPTypes = @[@(NEHotspotConfigurationEAPTypeEAPTLS)]; eapSettings.outerIdentity = @"xxxx"; [eapSettings setTrustedServerCertificates:@[(__bridge id)caCertificateRef]]; [eapSettings setIdentity: (__bridge id)userIdentity]; NEHotspotConfiguration *hotspotConfig = [[NEHotspotConfiguration alloc] initWithSSID:@"ssidName" eapSettings:eapSettings]; [[NEHotspotConfigurationManager sharedManager] applyConfiguration:hotspotConfig completionHandler:^(NSError * _Nullable error) { NSLog(@"failure"); }]; CFRelease(items); } else { NSLog(@"Error opening Certificate."); } if (caCertificateRef) CFRelease(caCertificateRef); if (userIdentity) CFRelease(userIdentity);
问题原因
错误码-25300对应errSecItemNotFound,核心原因是:
- 临时解析的
SecIdentityRef和SecCertificateRef仅存在于内存中,未存入系统钥匙串 NEHotspotConfiguration要求EAP认证使用的身份和可信证书必须是系统钥匙串中的持久化条目
解决方案
1. 将证书/身份存入系统钥匙串
解析PKCS#12和CA证书后,将它们添加到系统钥匙串,生成持久化引用:
// 存储CA证书到钥匙串 if (caCertificateRef) { NSMutableDictionary *caAddQuery = [NSMutableDictionary dictionary]; caAddQuery[(__bridge id)kSecClass] = (__bridge id)kSecClassCertificate; caAddQuery[(__bridge id)kSecValueRef] = caCertificateRef; caAddQuery[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly; OSStatus caAddStatus = SecItemAdd((__bridge CFDictionaryRef)caAddQuery, NULL); if (caAddStatus != errSecSuccess && caAddStatus != errSecDuplicateItem) { NSLog(@"Failed to add CA cert to keychain: %d", (int)caAddStatus); } } // 存储身份到钥匙串 if (userIdentity) { NSMutableDictionary *identityAddQuery = [NSMutableDictionary dictionary]; identityAddQuery[(__bridge id)kSecClass] = (__bridge id)kSecClassIdentity; identityAddQuery[(__bridge id)kSecValueRef] = userIdentity; identityAddQuery[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly; OSStatus identityAddStatus = SecItemAdd((__bridge CFDictionaryRef)identityAddQuery, NULL); if (identityAddStatus != errSecSuccess && identityAddStatus != errSecDuplicateItem) { NSLog(@"Failed to add identity to keychain: %d", (int)identityAddStatus); } }
2. 从钥匙串获取持久化身份引用
避免直接使用内存中的SecIdentityRef,改为从钥匙串查询已存储的身份:
// 查询钥匙串中的身份 SecIdentityRef storedIdentity = nil; NSMutableDictionary *query = [NSMutableDictionary dictionary]; query[(__bridge id)kSecClass] = (__bridge id)kSecClassIdentity; query[(__bridge id)kSecReturnRef] = @YES; // 可添加精确查询条件,比如证书主题 // query[(__bridge id)kSecAttrSubject] = (__bridge id)SecCertificateCopySubjectDN(SecIdentityCopyCertificate(userIdentity)); OSStatus queryStatus = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&storedIdentity); if (queryStatus == errSecSuccess) { eapSettings.identity = (__bridge id)storedIdentity; CFRelease(storedIdentity); } else { NSLog(@"Failed to retrieve identity from keychain: %d", (int)queryStatus); }
3. 配置应用钥匙串权限
在Xcode中为应用添加Keychain Sharing能力(Targets -> Signing & Capabilities -> 添加Keychain Sharing),确保应用拥有钥匙串访问权限。
4. 修正代码变量错误
原代码中误将SecIdentityRef声明为SecCertificateRef,需修正为SecIdentityRef userIdentity = nil;;同时注意PKCS#12文件后缀应为.p12而非.cer。
内容的提问来源于stack exchange,提问作者tom

