iOS证书导入-.p12与NSData

iOS证书导入-.p12与NSData,ios,objective-c,ssl,ssl-certificate,Ios,Objective C,Ssl,Ssl Certificate,我在iOS中使用客户端证书时遇到问题 当我在应用程序中存储.p12文件并按如下方式导入时: -(void)importCertificateToKeychain:(NSURL *)url withPassword:(NSString *)password name:(NSString *)name { importedItems = NULL; NSD

我在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,而是“提取”的证书。(我不控制服务器端)

这就是为什么我也尝试使用

    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 ---)
有人能看出我的方法有什么问题吗?我真的认为这应该特别适用于使用Credential:forAuthenticationChallenge是正确的证书。它怎么可能消失

thx m


注意:在不同的应用程序(与第一个共享密钥链)中,我可以看到密钥链中存储了两个相同的身份和一个证书。(但两种身份都不起作用)

也许我的代码的某些部分可以帮助您解决问题。请随时提供一些反馈

我也有同样的问题。最后,我通过仔细查看身份,解决了SSL握手问题。我意识到从我的密钥链中提取了两个身份(这是我保存DER编码的客户端证书进行身份验证的地方)

这是我现在的工作代码:

  • 将证书存储到密钥链

  • 使用NSURLCredentials进行身份验证质询

  • 致意

    -(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];
    }