【发布时间】:2017-09-01 10:05:18
【问题描述】:
这是我已经尝试过的:
第一个想法是,只需将两者放入一个数组中,将该数组与 kSecUseItemList 一起使用,因此钥匙串调用将仅对该数组中的项目进行操作,而不是对真正的钥匙串进行操作,然后像这样获得身份:
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
);
原来这行不通。引用文档:
@常数
kSecUseItemList指定一个字典键,其值为CFArray的项目。如果提供,则将此数组视为 如果调用的 API 是,所有可能要搜索或添加的项目SecItemAdd。此数组中的项目可能是SecKeyRef类型,SecCertificateRef、SecIdentityRef或CFDataRef(对于持久性 项目引用。) 数组中的项目必须全部相同 输入。提供此属性时,不会搜索钥匙串。
嗯,它们不是同一类型,所以这行不通。
我的第二次尝试是将这两个项目添加到钥匙串(使用SecItemAdd()),它按预期工作,然后找到证书(使用SecItemCopyMatching()),它也成功并最终使用:
SecIdentityRef identity = NULL;
OSStatus copyStatus = SecIdentityCreateWithCertificate(NULL, cert, &identity);
但是 errKCItemNotFound 失败了。
查看 Keychain Access 应用程序中的项目,证书和私钥都在那里,它们都是正确的,但它们没有显示为形成身份(它们没有在“我的证书”下列出,证书只是列在“证书”下,密钥在“密钥”下)。
好的,我做错了什么或者我错过了什么重要的步骤?
如果我将密钥导出为 PKCS#8 并将证书导出为 DER 表示法,然后在命令行上使用 openssl 将两者组合成一个 PKCS#12 文件并使用 Keychain Access 导入该文件,然后它们将显示为钥匙串访问中的身份和此身份也可以正常工作(因此私钥实际上是证书中公钥的正确密钥)。但这并不是一个真正的选择,因为我的代码不能依赖于 OpenSSL,并且理想情况下可以移植到 iOS。
据我了解文档,身份匹配是通过匹配公钥哈希完成的,因此这可能与我的问题有关。系统如何知道我的公钥 SecKeyRef 的哈希值,这只是一个原始的私有 RSA 密钥?
文档还说我可以直接用SecAddItem() 添加SecIdentityRef,在这种情况下,我想一切都可能按预期工作(无法添加身份本身,将添加证书和私钥,但我认为以这种方式添加它们时身份绑定会没问题),但这听起来像是一个先有鸡还是先有蛋的问题,因为我首先如何获得该身份引用?
我不明白为什么没有 SecCreateIdentity(...) 函数只在输入时接受 SecCertificateRef 和 SecKeyRef 并在输出时返回 SecIdentityRef。
更新
这是我在SecKey.h 找到的一些有趣信息:
@constant
kSecKeyLabel类型 blob,用于私钥和公钥 这包含公钥的哈希值。这是用来 关联证书和密钥。它的值与值匹配 证书的kSecPublicKeyHashItemAttr和已使用 从证书和密钥构造身份。 对于对称密钥,这是密钥的创建者 在生成密钥调用期间传入。
此值设置不正确。证书中的公钥散列为0x966C57...,但我的私钥包含0x097EAD...,这看起来像私钥本身的散列。如果我能以某种方式将此值设置为正确的值,我会尝试。
更新 2
这似乎是另一个死胡同。当我尝试在将密钥添加到钥匙串之前将kSecAttrApplicationLabel 与SecKeyUpdate() 一起设置时,我得到errKCItemNotFound,这是预期的,正如文档所述:
SecKeyRef实例表示存储在钥匙串中的钥匙 可以安全地转换为SecKeychainItemRef进行操作 钥匙扣项目。另一方面,如果密钥没有存储在 钥匙串,将对象转换为SecKeychainItemRef并传递 它对 Keychain Services 函数返回错误。
很公平。所以我首先添加密钥,然后从钥匙串中取回它,最后尝试更新kSecAttrApplicationLabel,但这也失败了,这个错误是errKCNoSuchAttr。
哦,如果有人想知道我为什么要更新 kSecAttrApplicationLabel,当我说属性在我的第一次更新中命名为 kSecKeyLabel:kSecKeyLabel 是旧属性枚举的枚举值,Apple 用于各种已全部弃用的 API 调用。新的 API 调用(如 SecItemUpdate())与字典一起使用,并且由于使用枚举值作为字典键有点难看,Apple 定义了一组新的字典键 CFStringRef。
@constant
kSecAttrApplicationLabel指定字典键 其值为键的应用程序标签属性。 这与kSecAttrLabel不同(旨在 人类可读)。该属性用于查找键 以编程方式;特别是对于类的键kSecAttrKeyClassPublic和kSecAttrKeyClassPrivate, 该属性的值是公钥的哈希值。 此项目是CFDataRef的类型。 旧版密钥可能在此字段中包含 UUID,如CFStringRef。
所以这似乎是要更新的正确属性,不是吗?除了错误暗示该项目不存在此类属性外。即使同一个头文件明确将此属性列为SecKeyRef 项目的可能属性:
kSecClassKey物品属性:kSecAttrAccess(仅限 OS X)kSecAttrAccessControlkSecAttrAccessGroup(iOS;如果指定了 kSecAttrSynchronizable 也适用于 OS X)kSecAttrAccessible(iOS;如果指定了 kSecAttrSynchronizable 也适用于 OS X)kSecAttrKeyClasskSecAttrLabelkSecAttrApplicationLabel
[...等等...]
更新 3
我得到的第一个答案建议改用SecItemCopyMatching(),但是,请理解这段代码:
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
);
实际上与他的代码功能相同:
SecIdentityRef result = NULL;
OSStatus status = SecIdentityCreateWithCertificate(
NULL, cert &result
);
后一个只是较旧的 API 调用(较旧,但未弃用),当时钥匙串访问仅限于使用 Sec...RefCoreFoundation“对象”(Apple 试图在纯 C 中模仿一点 OO) ,而第一个是较新的 API,您通常只使用钥匙串项目的字典表示形式(因为它将免费转换为 Obj-C,当您使用 ARC 时,您只需要一些桥接转换),但您也可以选择回退到CoreFoundation“对象”(使用kSecMatchItemList、kSecUseItemList或kSecReturnRef等属性时)。我实际上很确定实际上只有一个真正的 API,而另一个只是在另一个之上实现(取决于哪个在哪个之上,新的可能只是为了方便而存在,也可能是旧的只是为了向后兼容而保留的)。
【问题讨论】:
标签: macos certificate keychain private-key