为什么 SecPKCS12Import 会自动将 SecIdentities 添加到钥匙串中?

Why does SecPKCS12Import automatically add SecIdentities to the Keychain?

SecPKCS12Import 上的 documentation 说明如下:

[…] You can then use the Keychain Services API (see Keychain Services Reference) to put the identities and associated certificates in the keychain.

这意味着“items”参数(该函数的第三个参数)中返回的项目不应自动添加到钥匙串中。但是,我发现在使用该功能时,这些项目会自动添加到钥匙串中。如果我尝试使用 SecItemAdd 添加它们,我会得到 errSecDuplicateItem。

这是一个错误还是应该这样?为什么会自动添加项目?

下面是一些示例代码:

NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys:@"password", (id)kSecImportExportPassphrase, nil];
CFArrayRef items_ = NULL;
OSStatus ret = SecPKCS12Import((CFDataRef)pkcs12data /* get this from somewhere … */, (CFDictionaryRef)options, &items_);

如果您使用该代码然后打开钥匙串访问,您会看到证书和私钥已添加到钥匙串中。

此致, 大卫.

似乎 Apple 的文档可能已过时 link (SecPKCS12Import),因为此 link https://developer.apple.com/library/ios/qa/qa1745/_index.html 提到 "reading in a PKCS#12-formatted blob and then importing the contents of the blob into the app's keychain using the function SecPKCS12Import..."

根据文档修订日期,QA1745 比证书、密钥和信任服务参考更新。

SecPKCS12Import 不会将项目添加到钥匙串。但是,它会查看钥匙串以查看导入的项目是否已经存在。如果它找到现有项目,将为 SecIdentityRef (SecCertificateRef and SecKeyRef) 返回它们。这就是为什么在调用SecPKCS12Import.

后调用SecItemAdd可以得到errSecDuplicateItem的原因

调试时,您可能希望使用如下代码删除钥匙串中的所有内容:

void _EraseKeychain()
{
    NSMutableArray *toDelete = [NSMutableArray array];
    NSArray *classes = @[(__bridge id)kSecClassCertificate,
                         (__bridge id)kSecClassKey,
                         (__bridge id)kSecClassIdentity,
                         (__bridge id)kSecClassInternetPassword,
                         (__bridge id)kSecClassGenericPassword];
    NSMutableDictionary *query = [NSMutableDictionary dictionary];
    query[(id)kSecClass] = (__bridge id)kSecClassIdentity;
    query[(id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
    query[(id)kSecReturnPersistentRef] = @YES;
    id class;
    for( class in classes )
    {
        query[(__bridge id)kSecClass] = class;
        CFTypeRef items = nil;
        OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)query, &items);
        if( result == errSecSuccess )
        {
            [toDelete addObjectsFromArray:(__bridge NSArray*)items];
            CFRelease(items);
        }
    }
    id deleteRef;
    for( deleteRef in toDelete )
    {
        NSString *objectKind = @"unknown";
        if( CFGetTypeID(deleteRef) == CFDataGetTypeID() )
        {
            objectKind = [[NSString alloc] initWithUTF8String:(char *)[(__bridge NSData*)deleteRef bytes]];
        }
        NSDictionary *delRequest = @{(id)kSecValuePersistentRef:deleteRef};
        OSStatus deleteResult = SecItemDelete((__bridge CFDictionaryRef)delRequest);
        if( deleteResult == errSecSuccess )
            NSLog(@"Deleted item(%@) with persistent ref %@", objectKind, deleteRef);
        else if( deleteResult == errSecItemNotFound )
            NSLog(@"Already deleted item(%@) with persistent ref %@", objectKind, deleteRef);
        else
            NSLog(@"Can't delete keychain item(%@) with persistent ref %@", objectKind, deleteRef);
    }
}

Is this a bug or should it be this way?

这不是错误,只是文档不正确。好吧,它对 iOS 是正确的,只是对 macOS 不正确。

Why are the items automatically added?

这是由该功能在 macOS 中的实现方式引起的。 the implementation中的评论揭示了原因:

// SecPKCS12Import is implemented on Mac OS X in terms of the existing
// SecKeychainItemImport API, which supports importing items into a
// specified keychain with initial access control settings for keys.

SecPKCS12Import 在 macOS 中只是 SecKeychainItemImport 的包装器,正如函数名称所暗示的那样,此函数导入到钥匙串中。这也解释了以下代码:

if (!importKeychain) {
    // SecKeychainItemImport requires a keychain, so use default
    status = SecKeychainCopyDefault(&importKeychain);
}

在 iOS 上,该函数是独立实现的,而不是作为包装器实现的,因为 SecKeychainItemImport 在 iOS 上什至不可用。

但是如果这对您来说是个问题,有一个解决办法。许多人通过创建一个临时钥匙串来解决这个问题,它永远不会对系统或用户可见(因此对应用程序或钥匙串访问也不可见),当然,这也可以,但有点一个丑陋的骇客。

更好:使用 SecItemImport 并作为导入格式使用 kSecFormatPKCS12,然后您还可以获得已解析的身份,但除非您要求,否则不会在任何地方导入任何内容。