如何从SecCertificateRef和SecKeyRef获取SecIdentityRef [英] How to get a SecIdentityRef from a SecCertificateRef and a SecKeyRef

查看:236
本文介绍了如何从SecCertificateRef和SecKeyRef获取SecIdentityRef的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我已经尝试过的:

第一个想法是,将它们都放入一个数组中,并将该数组与kSecUseItemList一起使用,因此,钥匙串调用将仅在此数组中的项目上起作用,而不是在真正的钥匙串上起作用,然后获得这样的标识:

First idea was, just put both into an array, use that array with kSecUseItemList, so keychain calls will operate only on the items in this array, not on real keychains and then get the identity like this:

NSDictionary * searchQuery = @{
    (__bridge id)kSecClass:(__bridge id)kSecClassIdentity,
    (__bridge id)kSecUseItemList:@[(__bridge id)key, (__bridge id)cert],
    (__bridge id)kSecReturnRef:@YES
};
CFTypeRef foundItem = NULL;
OSStatus copyStatus = SecItemCopyMatching(
    (__bridge CFDictionaryRef)searchQuery, &foundItem
);

原来,这不起作用.引用文档:

Turned out that this doesn't work. To quote from the docs:

@ 常数 kSecUseItemList 指定一个字典键,其值为a CFArray个项目.如果提供,则将此数组视为一组 所有可能要搜索的项目,或者如果被调用的API是,则添加 SecItemAdd.此数组中的项目可能是SecKeyRef类型, SecCertificateRefSecIdentityRefCFDataRef(对于持久性 项目引用.)数组中的项目必须全部相同 输入.提供此属性后,将不会搜索任何钥匙串.

@constant kSecUseItemList Specifies a dictionary key whose value is a CFArray of items. If provided, this array is treated as the set of all possible items to search, or add if the API being called is SecItemAdd. The items in this array may be of type SecKeyRef, SecCertificateRef, SecIdentityRef, or CFDataRef (for a persistent item reference.) The items in the array must all be of the same type. When this attribute is provided, no keychains are searched.

好吧,它们不是同一类型,所以这行不通.

Well, they are not of the same type, so this cannot work.

我的第二次尝试是将两个项目都添加到钥匙串中(使用SecItemAdd()),按预期工作,然后找到证书(使用SecItemCopyMatching()),该证书也成功了,最后使用以下命令获取我的身份:

My second attempt was to add both items to keychain (using SecItemAdd()), which works as expected, then find the certificate (using SecItemCopyMatching()) which also succeeds and finally getting my identity using:

SecIdentityRef identity = NULL;
OSStatus copyStatus = SecIdentityCreateWithCertificate(NULL, cert, &identity);

但是用errKCItemNotFound失败.

查看钥匙串访问"应用程序中的项目,证书和私钥都在这里,它们都是正确的,但是它们不会显示为形成身份(它们未列在我的证书"下,证书仅列在证书"下,密钥在密钥"下.

Looking at the items in Keychain Access app, the certificate and the private key are both there, they are both correct but they are not displayed as forming an Identity (they are not listed under "My Certificates", the cert is only listed under "Certificates" and the key under "Keys").

好吧,我做错了什么,或者我错过了重要的一步?

Okay, what am I doing wrong or what important step am I missing?

如果我将密钥导出到PKCS#8,并且将证书导出到DER表示法,则在命令行上使用openssl将两者都组合到PKCS#12文件中,并通过Keychain Access导入该文件,则它们将显示为钥匙串访问中的身份,此身份也可以正常工作(因此,私钥实际上是证书中公钥的正确密钥).但这并不是真正的选择,因为我的代码不得依赖OpenSSL,并且理想情况下可移植到iOS.

If I export the key to PKCS#8 and the cert to DER notation, then use openssl on command line to combine both into a PKCS#12 file and import that file with Keychain Access, then they are displayed as an Identity in keychain access and this identity also works correctly (so the private key is really the correct key for the public key in the cert). But this is not really an option, as my code must not rely on OpenSSL and would ideally be portable to iOS.

据我了解的文档,身份匹配是通过匹配公钥哈希来完成的,因此可能与我的问题有关.系统如何为我SecKeyRef知道公钥的哈希,这仅仅是一个原始的RSA私钥?

As far as I understood the documentation, the identity matching is done by matching public key hashes, so that could be related to my problem. How would the system know the hash of the public key for me SecKeyRef, which is only a raw private RSA key?

文档还说,我可以直接用SecAddItem()添加SecIdentityRef,在这种情况下,我猜一切都可以按预期进行(无法添加身份本身,将添加证书和私钥,但我认为以这种方式添加它们后,身份绑定就可以了),但这听起来像是个鸡蛋问题,因为我将如何首先获得该身份引用?

The docs also say that I can add a SecIdentityRef directly with SecAddItem(), in which case I guess everything would probably work as expected (the identity itself cannot be added, the cert and the private key will be added, but I assume the identity binding will be okay when adding them that way), but that sounds like a chicken-egg-problem, since how would I get that identity reference in the first place?

我不明白为什么没有SecCreateIdentity(...)函数只在输入上使用SecCertificateRefSecKeyRef并在输出上返回SecIdentityRef.

I cannot understand why there is no SecCreateIdentity(...) function that simply takes a SecCertificateRef and SecKeyRef on input and returns a SecIdentityRef on output.


这是我在SecKey.h中找到的一些有趣信息:

Here's some interesting info I found in SecKey.h:

@constant kSecKeyLabel 类型blob,用于私钥和公钥 它包含公钥的哈希.这用来 关联证书和密钥.其值与值匹配 证书的kSecPublicKeyHashItemAttr 通过证书和密钥来构造身份. 对于对称密钥,这就是密钥的创建者 在生成键调用期间传入.

@constant kSecKeyLabel type blob, for private and public keys this contains the hash of the public key. This is used to associate certificates and keys. Its value matches the value of the kSecPublicKeyHashItemAttr of a certificate and it's used to construct an identity from a certificate and a key. For symmetric keys this is whatever the creator of the key passed in during the generate key call.

此值未正确设置.证书中的公钥散列为0x966C57...,但是我的私钥包含0x097EAD...,这看起来像私钥本身的哈希.我将尝试以某种方式将此值设置为正确的值.

This value is not correctly set. The public key in the certs hashes to 0x966C57... but my private key contains 0x097EAD... and this looks like the hash of the private key itself. I will try if I can somehow set this value to the correct one.


这似乎是另一个死胡同.当我尝试在将键上的kSecAttrApplicationLabel设置为kSecAttrApplicationLabel并将其添加到钥匙串中时,会得到errKCItemNotFound,这是预期的,如文档所述:

That seems to be another dead end. When I try to set kSecAttrApplicationLabel with SecKeyUpdate() on a key prior to adding it to keychain, I get errKCItemNotFound, which is expected, as the documentation says:

一个SecKeyRef实例,表示存储在钥匙串中的钥匙 可以安全地投射到SecKeychainItemRef进行操作 钥匙扣项目.另一方面,如果密钥未存储在中,则为 钥匙串,将对象投射到SecKeychainItemRef并传递 将其发送给钥匙串服务函数会返回错误.

A SecKeyRef instance that represents a key that is stored in a keychain can be safely cast to a SecKeychainItemRef for manipulation as a keychain item. On the other hand, if the key is not stored in a keychain, casting the object to a SecKeychainItemRef and passing it to Keychain Services functions returns errors.

足够公平.因此,我首先添加了密钥,然后从密钥链中将其取回,最后尝试更新kSecAttrApplicationLabel,但这也失败了,该错误是errKCNoSuchAttr.

Fair enough. So I first add the key, then retrieve it back from keychain and finally try to update kSecAttrApplicationLabel, but this fails as well, this the error is errKCNoSuchAttr.

哦,以防万一有人怀疑我为什么要更新kSecAttrApplicationLabel,当我在第一次更新中说该属性命名为kSecKeyLabel时:kSecKeyLabel是Apple在各种属性中使用的旧属性枚举的枚举值已全部弃用的API调用.新的API调用(如SecItemUpdate())可用于词典,并且使用枚举值作为字典键有点难看,Apple定义了一组新的字典键,即CFStringRef.

Oh, in case anyone wonders why I'm updating kSecAttrApplicationLabel when I said the attribute is named kSecKeyLabel in my first update: The kSecKeyLabel is an enumeration value of the old attribute enumerations that Apple used with various API calls that have all been deprecated. The new API calls (like SecItemUpdate()) work with dictionaries and as using enum values as dictionary keys is a bit ugly, Apple defined a new set of dictionary keys that are CFStringRef.

@constant kSecAttrApplicationLabel 指定字典键 其值是密钥的应用程序标签属性. 这与kSecAttrLabel(旨在 人类可读的).此属性用于查找密钥 以编程方式特别是对于班级的钥匙 kSecAttrKeyClassPublickSecAttrKeyClassPrivate, 此属性的值是公钥的哈希值. 此项是CFDataRef的类型. 旧版密钥可能在此字段中包含作为UUID.

@constant kSecAttrApplicationLabel Specifies a dictionary key whose value is the key's application label attribute. This is different from the kSecAttrLabel (which is intended to be human-readable). This attribute is used to look up a key programmatically; in particular, for keys of class kSecAttrKeyClassPublic and kSecAttrKeyClassPrivate, the value of this attribute is the hash of the public key. This item is a type of CFDataRef. Legacy keys may contain a UUID in this field as a CFStringRef.

所以这似乎是要更新的正确属性,不是吗?除了该错误意味着该项目不存在这样的属性之外.即使同一头文件显式将此属性列为SecKeyRef项目的可能属性:

So this seems to be the correct attribute to update, doesn't it? Except that the error implies no such attribute exists for the item. Even though the same header file explicitly lists this attribute as a possible attribute for SecKeyRef items:

kSecClassKey项目属性:
    kSecAttrAccess(仅OS X)
    kSecAttrAccessControl
    kSecAttrAccessGroup (iOS;如果指定了kSecAttrSynchronizable,则也为OS X)
    kSecAttrAccessible (iOS;如果指定了kSecAttrSynchronizable,则也为OS X)
    kSecAttrKeyClass
    kSecAttrLabel
    kSecAttrApplicationLabel
    [...依此类推...]

kSecClassKey item attributes:
    kSecAttrAccess (OS X only)
    kSecAttrAccessControl
    kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable specified)
    kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable specified)
    kSecAttrKeyClass
    kSecAttrLabel
    kSecAttrApplicationLabel
     [... and so on ...]


我建议使用的第一个答案是SecItemCopyMatching(),但是,请理解以下代码:

The first answer I got suggested to use SecItemCopyMatching() instead, however, please understand that this code:

CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching(
    (__bridge CFDictionaryRef)@{
        (__bridge id)kSecClass:(__bridge id)kSecClassIdentity,
        (__bridge id)kSecMatchItemList:@[(__bridge id)cert],
        (__bridge id)kSecReturnRef:@YES
     }, &result
);

在功能上与他的代码完全相同:

is really functional identical to his code:

SecIdentityRef result = NULL;
OSStatus status = SecIdentityCreateWithCertificate(
    NULL, cert &result
);

后一个只是较旧的API调用(较旧,但不建议弃用),当时钥匙串访问仅限于使用Sec...Ref CoreFoundation对象"(Apple尝试在纯C中模仿一些OO). ,而第一个是较新的API,在该API中,您通常仅使用钥匙串项的字典表示形式(因为这对Obj-C可以免费转换,使用ARC时只需要一些桥接转换),但是您也可以选择退回到CoreFoundation的对象"(使用诸如kSecMatchItemListkSecUseItemListkSecReturnRef之类的属性时).我实际上很确定,实际上只有一个真实的API,而另一个仅在另一个API之上实现(取决于哪个API依赖于哪个API,为了方便起见,可能只是存在一个较新的API)只是为了向后兼容而保留).

The later one is just the older API call (older, but not deprecated) from the time where keychain access was limited to working with Sec...Ref CoreFoundation "objects" (Apple's attempt to mimic a bit of OO in pure C), whereas the first one is the newer API where you usually only work with dictionary representation of keychain items (as that casts toll-free to Obj-C, you just need some bridging casts when you use ARC) but where you also have the choice to fall back to CoreFoundation "objects" (when using attributes like e.g. kSecMatchItemList, kSecUseItemList, or kSecReturnRef). I'm' actually rather sure that in fact there's just one real API and the other one is just implemented on top of the other one (depending which one on top of which one, the newer one may just exist for convenience or the older one was just kept for backward compatibility).

推荐答案

诀窍是先导出内存中的密钥,然后将其直接重新导入到钥匙串中,而不仅仅是将其添加到其中.参见下面的代码(小心,它是C ++):

The trick is to export the in-memory key first and then re-import it directly to the keychain instead of just adding it there. See the code below (careful, it's C++):

static OSStatus AddKeyToKeychain(SecKeyRef privateKey, SecKeychainRef targetKeychain)
{
    // This is quite similar to pal_seckey's ExportImportKey, but
    // a) is used to put something INTO a keychain, instead of to take it out.
    // b) Doesn't assume that the input should be CFRelease()d and overwritten.
    // c) Doesn't return/emit the imported key reference.
    // d) Works on private keys.
    SecExternalFormat dataFormat = kSecFormatWrappedPKCS8;
    CFDataRef exportData = nullptr;

    SecItemImportExportKeyParameters keyParams = {};
    keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
    keyParams.passphrase = CFSTR("ExportImportPassphrase");

    OSStatus status = SecItemExport(privateKey, dataFormat, 0, &keyParams, &exportData);

    SecExternalFormat actualFormat = dataFormat;
    SecExternalItemType actualType = kSecItemTypePrivateKey;
    CFArrayRef outItems = nullptr;

    if (status == noErr)
    {
        status =
            SecItemImport(exportData, nullptr, &actualFormat, &actualType, 0, &keyParams, targetKeychain, &outItems);
    }

    if (exportData != nullptr)
        CFRelease(exportData);

    CFRelease(keyParams.passphrase);
    keyParams.passphrase = nullptr;

    if (outItems != nullptr)
        CFRelease(outItems);

    return status;
}

代码已被使用从这里.

这篇关于如何从SecCertificateRef和SecKeyRef获取SecIdentityRef的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆