You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS通过NEHotspotConfiguration实现EAP证书认证连接失败求助

iOS NEHotspotConfiguration EAP-TLS证书认证失败问题

问题概述

通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 20:55:37