将客户端身份复制到Mac OS上的私钥匙

时间:2017-02-13 13:42:46

标签: objective-c swift macos security keychain

背景

我正在使用大型多平台库,其中使用了客户端身份。要求是应用程序私下维护客户端身份,并且可以从系统存储中导入客户端身份(如果是Mac OS默认密钥链)。

所以基本上API必须涵盖以下

  • 从默认钥匙串设置客户端身份
  • 设置客户端身份表单文件(系统keychian不应该看到它)
  • 删除客户端身份(这不应该破坏其他应用程序)
  • 如果身份将从默认钥匙串中删除(例如使用Keychain Access),我的应用程序不应受到影响

基本问题

最初我在一些私人加密文件中计划了商店标识,但事实证明Apple API不允许在没有钥匙串的情况下导入客户端身份。

所以我决定我的图书馆将维护私人钥匙串。如果导入身份表单文件,它很简单,它确实有效:

NSDictionary *options =  
@{  
    (id)kSecImportExportPassphrase:password.stringValue,  
    (id)kSecImportExportKeychain:(__bridge id)keychain,  
};  

CFArrayRef arrayResult = NULL;  
OSStatus status = SecPKCS12Import((CFDataRef)pkcs12,  
                                  (CFDictionaryRef)options,  
                                  &arrayResult);  
if (status != errSecSuccess)  

现在的问题是如何将客户端身份表格默认的keychian复制到私人钥匙串?

这是我用来创建钥匙串的代码:

- (void)setupOpenMyKeychain  
{  
    NSString *keychainPath = [self keychainPath];  
    NSString *pass = @"dasndiaisdfs"; // used only for testing  

    OSStatus status = SecKeychainCreate(keychainPath.UTF8String,  
                                        (UInt32)strlen(pass.UTF8String),  
                                        pass.UTF8String,  
                                        NO,  
                                        NULL,  
                                        &keychain);  

    if (status == errSecDuplicateKeychain)  
    {  
        status = SecKeychainOpen(keychainPath.UTF8String, &keychain);  
        if (status == errSecSuccess)  
        {  
            status = SecKeychainUnlock(keychain,  
                                       (UInt32)strlen(pass.UTF8String),  
                                       pass.UTF8String,  
                                       TRUE);  
            if (status != errSecSuccess)  
            {  
                [self showOSStatusFailureAlert: status forAction: @"Unlock failure"];  
            }  
        }  
    }  
    if (status != errSecSuccess)  
    {  
        [self showOSStatusFailureAlert: status forAction: @"Open/Create failure"];  
    }  
}  

现在我在默认钥匙串中有SecIdentityRef和SecCertificate(在CFArrayRef中),我想将它复制到我的自定义keychian:

- (NSData *)persistantRefFor: (id)item  
{  
    CFTypeRef result = NULL;  
    NSDictionary *dic =  
    @{  
      (id)kSecValueRef:(id)item,  
      (id)kSecReturnPersistentRef:(id)kCFBooleanTrue,  
      //(id)kSecUseKeychain:(__bridge id)keychain,  
      };  

    OSStatus status = SecItemCopyMatching((CFDictionaryRef)dic,  
                                          &result);  

    if (status != errSecSuccess)  
    {  
        [self showOSStatusFailureAlert: status  
                             forAction: @"Failed to find reference"];  
        return nil;  
    }  
    return (__bridge_transfer NSData *)result;  
}  

-(OSStatus)copyItemWithPersistantRef:(NSData *)persistantReference  
{  
    SecKeychainItemRef item = NULL;  
    OSStatus result = SecKeychainItemCopyFromPersistentReference((__bridge CFDataRef)persistantReference, &item);  
    if (result != errSecSuccess)  
    {  
        [self showOSStatusFailureAlert: result forAction: @"Get item from reference"];  
        return result;  
    }  
    CFAutorelease(item);  

    SecKeychainRef soruceKeychain = NULL;  
    result = SecKeychainItemCopyKeychain(item, &soruceKeychain);  
    if (result == errSecSuccess)  
    {  
        if (soruceKeychain == keychain)  
        {  
            CFRelease(soruceKeychain);  
            // item is already in desired keychain  
            return result;  
        }  
    } else {  
        [self showOSStatusFailureAlert: result forAction: @"Fetching source keychain"];  
        return result;  
    }  

    SecAccessRef access = NULL;  
    result = SecKeychainCopyAccess(keychain, &access);  
    if (result != errSecSuccess)  
    {  
        [self showOSStatusFailureAlert: result forAction: @"Get Access object"];  
        // return result;  
    } else {  
        CFAutorelease(access);  
    }  

    SecKeychainItemRef itemCopy = NULL;  
    result = SecKeychainItemCreateCopy(item, keychain, access, &itemCopy);  
    if (result != errSecSuccess)  
    {  
        [self showOSStatusFailureAlert: result forAction: @"Create copy in private keychain"];  
        return result;  
    }  
    CFAutorelease(itemCopy);  

    NSLog(@"Copied item %@", itemCopy);  
    NSData *copyPersistantRef = [self persistantRefFor: (__bridge id)itemCopy];  
    NSLog(@"Old persisntatn reference %@\n"  
           "New persisntatn reference %@\n"  
           "%@", persistantReference, copyPersistantRef,  
          [persistantReference isEqualToData: copyPersistantRef]?@"SAME":@"DIFFRENT");  

    return result;  
}  

- (IBAction)copyIdentityToPrivateKeychain:(id)sender  
{  
    if (!identity)  
    {  
        return;  
    }  
    NSData *perRef = [self persistantRefFor: (__bridge id)identity];  

    OSStatus status = [self copyItemWithPersistantRef: perRef];  
    if (status != errSecSuccess)  
    {  
        [self showOSStatusFailureAlert: status forAction: @"Copy Identity has failed"];  
    }  
}  

尝试将SecCertificateRef复制到我的私钥钥匙时。发生以下事情:

  1. SecKeychainCopyAccess失败,显示“错误:0xFFFFFFFC -4功能或操作未实现”。 - 代码继续执行,因为当达到SecKeychainItemCreateCopy(应该工作的文档sugest)时,显然NULL值很好。
  2. SecKeychainItemCreateCopy传递确定
  3. NSData *copyPersistantRef = [self persistantRefFor: (__bridge id)itemCopy];失败。我无法获得对复制项目的持久引用。没有此引用,我无法在应用程序启动时加载适当的证书
  4. 尝试复制SecIdentityRef时更糟糕,

      带有Error: 0xFFFF9D28 -25304 The specified item is no longer valid. It may have been deleted from the keychain. 的SecKeychainItemCopyKeychain上的
    1. 代码失败
    2. 当我被迫跳过此错误以达到SecKeychainItemCreateCopy时,它失败并出现同样的错误:Error: 0xFFFF9D28 -25304 The specified item is no longer valid. It may have been deleted from the keychain.
    3. 根据它假设工作的文档的底线,但它不起作用。可能我做错了什么,但我无法找到问题所在。

      我们将不胜感激。

      同样的问题我posted on Apple forum

      需要支持OS X 10.10。

      这是我用于快速测试的small test application

1 个答案:

答案 0 :(得分:0)

使用 SecPKCS12Import 代替 SecItemImport。将 kSecFormatPKCS12 作为 inputFormat 传递,kSecItemTypeAggregate 作为 itemType(要求身份或 kSecItemTypeUnknown,它会尝试为您检测类型)并将 importKeychain 设置为 NULL,这样可以防止导入到钥匙串。