我在iOS中使用客户端证书时遇到问题。
当我在我的应用程序中存储.p12文件并将其导入时:
-(void)importCertificateToKeychain:(NSURL *)url
withPassword:(NSString *)password
name:(NSString *)name {
importedItems = NULL;
NSData* data = [url isFileURL] ? [NSData dataWithContentsOfFile:url.path] : [NSData dataWithContentsOfURL:url];
err = SecPKCS12Import(
(__bridge CFDataRef) data,
(__bridge CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
password, kSecImportExportPassphrase,
nil
],
&importedItems
);
if (err == noErr) {
for (NSDictionary * itemDict in (__bridge id) importedItems) {
SecIdentityRef identity;
identity = (__bridge SecIdentityRef) [itemDict objectForKey:(__bridge NSString *) kSecImportItemIdentity];
NSMutableDictionary *addItemDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)identity, kSecValueRef,
nil
];
[addItemDictionary setValue:name forKey:(__bridge NSString *)kSecAttrLabel];
err = SecItemAdd((__bridge CFDictionaryRef)addItemDictionary, NULL);
}
这很好用,我可以用以下方法加载它:
-(NSURLCredential *)loadCertificateFromKeychain:(NSString *)name {
OSStatus err;
CFArrayRef latestIdentities;
NSMutableDictionary *filterDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassIdentity, kSecClass,
kSecMatchLimitAll, kSecMatchLimit,
kCFBooleanTrue, kSecReturnRef,
nil];
[filterDictionary setValue:name forKey:(__bridge NSString *)kSecAttrLabel];
err = SecItemCopyMatching((__bridge CFDictionaryRef)(filterDictionary),
(CFTypeRef *) &latestIdentities
);
SecIdentityRef identityRef = (SecIdentityRef)CFArrayGetValueAtIndex(latestIdentities, 0);
id certificates = nil;
NSURLCredential* credential = [NSURLCredential credentialWithIdentity:identityRef certificates:certificates persistence:NSURLCredentialPersistenceNone];
return credential;
}
然后我在
中使用凭证 [[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
但是当我得到相同的证书没有存储在.p12文件中但是从服务器以NSData格式存储时,这不再起作用了。我试图把收到的NSData放到SecPKCS12Import但它返回-26275所以我猜它不仅仅是p12的NSData,而只是#34;提取的#34;证书。(我不控制服务器端)
这也是我尝试使用
的原因 SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(certificateDataFromServer));
并用以下结果保存结果:
CFTypeRef cert = nil;
CFStringRef certLabel = CFStringCreateWithCString(
NULL, certLabelString,
kCFStringEncodingUTF8);
OSStatus err =
SecItemAdd((__bridge CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)
kSecClassCertificate, kSecClass,
kCFBooleanTrue, kSecReturnRef,
deviceCertificate, kSecValueRef,
certLabel,kSecAttrLabel,
nil],
&cert);
然后在authenticateForChallenge中我使用身份和证书
- (BOOL)authenticateForChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge previousFailureCount] > 0) {
return NO;
}
NSURLCredential *newCredential = nil;
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
SecIdentityRef identity = [self clientIdentity];
NSArray *certs = [self clientCertificates];
if (identity) {
newCredential = [NSURLCredential credentialWithIdentity:identity
certificates:certs
persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
return YES;
}
return NO;
}
}
但它不起作用。在调试中,我可以看到有正确的标识和一个正确证书的数组,但服务器上没有使用客户端证书,并且身份验证不起作用。 服务器日志:
TLSv1.2 - "POST /my_service/v1 HTTP/1.1" - --- - (certificate should be here instead of ---)
是否有人能够看到我的方法有什么问题?我真的认为这应该工作,特别是在useCredential:forAuthenticationChallenge是正确的证书。它怎么可能消失?
THX 米!
注意:在另一个应用程序(与第一个共享钥匙串)中,我可以看到存在于钥匙串中的两个相同的身份和一个证书。 (但是没有一个身份有效)
答案 0 :(得分:2)
我的代码的某些部分可能会帮助您解决问题。随意提供一些反馈。
我刚才有同样的问题。我终于设法通过仔细查看身份来解决SSL握手问题。我意识到从我的钥匙串中提取了两个身份(这是我持有我的DER编码的身份验证客户端证书)。
这是我现在的工作代码:
-(void) addCertToKeychain:(NSData*)certInDer
{
/*certInDer a Base64 encoded NSData from a String which I received from server ([[NSData alloc] initWithBase64Encoding:resultString];)*/
SecCertificateRef cert;
cert = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)(certInDer));
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:(__bridge id)kSecClassCertificate forKey:(__bridge id)kSecClass];
[dictionary setObject:(__bridge id)(cert) forKey:(__bridge id<NSCopying>)(kSecValueRef)];
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
assert(status == noErr || status == errSecDuplicateItem);
}
- (NSURLCredential*)getClientCertFromKeychain {
OSStatus err;
CFArrayRef latestIdentities;
NSURLCredential *credential=nil;
NSMutableDictionary *filterDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassIdentity, kSecClass,
kSecMatchLimitAll, kSecMatchLimit,
kCFBooleanTrue, kSecReturnRef,
nil];
err = SecItemCopyMatching((__bridge CFDictionaryRef)(filterDictionary),
(CFTypeRef *) &latestIdentities
);
/*This is the interesting part: The query might return more than one identity!!! */
//NSLog(@"Array Count %@",latestIdentities);
// Identity to obtain a certificate.
if(err == errSecSuccess) {
/*Here you can choose the identity to use, maybe you have to try according to your certificate structure*/
SecIdentityRef identityRef = (SecIdentityRef)CFArrayGetValueAtIndex(latestIdentities, 1);
SecCertificateRef certificate = nil;
//Create a new CertificateRef from identity
OSStatus status = SecIdentityCopyCertificate(identityRef, &certificate);
if(status == errSecSuccess){
const void *certs[] = { certificate };
CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
NSArray *certificatesForCredential = (__bridge NSArray *)certsArray;
//Fill the credential information
credential = [NSURLCredential credentialWithIdentity:identityRef
certificates:certificatesForCredential
persistence:NSURLCredentialPersistenceNone];
CFRelease(certsArray);
}
CFRelease(certificate);
CFRelease(identityRef);
}
return credential;
}
NSURLCredential *certData= [self getClientCertFromKeychain];
if(certData!=nil){
[[challenge sender] useCredential:certData forAuthenticationChallenge:challenge];
} else {
[challenge.sender cancelAuthenticationChallenge:challenge];
}
祝你好运