iOS证书导入 - .p12与NSData

时间:2014-04-28 19:38:57

标签: ios objective-c ssl ssl-certificate

我在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   米!

注意:在另一个应用程序(与第一个共享钥匙串)中,我可以看到存在于钥匙串中的两个相同的身份和一个证书。 (但是没有一个身份有效)

1 个答案:

答案 0 :(得分:2)

我的代码的某些部分可能会帮助您解决问题。随意提供一些反馈。

我刚才有同样的问题。我终于设法通过仔细查看身份来解决SSL握手问题。我意识到从我的钥匙串中提取了两个身份(这是我持有我的DER编码的身份验证客户端证书)。

这是我现在的工作代码:

  1. 将证书存储到钥匙串

  2. -(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);
    
        }
    
    1. 从Keychain获取所需的身份(让钥匙串处理困难的部分;)

    2. - (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;
      }
      
      1. 使用NSURLCredentials进行身份验证质询

      2. NSURLCredential *certData= [self getClientCertFromKeychain];
        
        if(certData!=nil){
            [[challenge sender] useCredential:certData forAuthenticationChallenge:challenge];
        } else {
            [challenge.sender cancelAuthenticationChallenge:challenge];
        }
        

        祝你好运