获取 Apple 钥匙串以识别 Bouncy Castle .NET 创建的 PKCS12 (.p12) 存储
我们的组织为多个客户端管理稳定的 iOS 应用程序,这意味着要处理许多不同的开发人员身份证书和推送通知证书。
我已经成功使用 Bouncy Castle C# Crypto API 简化了推送证书和私钥的管理通知,基本上消除了这种需要我们所有推送通知证书的钥匙串。
我想将其扩展到开发人员身份证书。目标是将每个开发人员身份的所有私钥和证书信息存储在数据库中。然后,当需要配置新的开发人员或构建机器时,服务器端代码可以将所有证书和私钥包装到一个 p12 存档中,并使用一个密码将其导入到目标 Mac 的钥匙串中。
不幸的是,Mac 钥匙串不喜欢我生成的 p12 文件。这很烦人,因为我可以成功地将这些文件导入到 Windows 证书管理器中。
我正在使用的代码(重要部分)如下所示:
private byte[] GetP12Bytes(List<DevIdentity> identities, string password)
{
Pkcs12Store store = new Pkcs12Store();
foreach(DevIdentity ident in identities)
{
// Easiest to create a Bouncy Castle cert by converting from .NET
var dotNetCert = new X509Certificate2(ident.CertificateBytes);
// This method (not shown) parses the CN= attribute out of the cert's distinguished name
string friendlyName = GetFriendlyName(dotNetCert.Subject);
// Now reconstitute the private key from saved value strings
BigInteger modulus = new BigInteger(ident.PrivateKey.Modulus);
BigInteger publicExponent = new BigInteger(ident.PrivateKey.PublicExponent);
BigInteger privateExponent = new BigInteger(ident.PrivateKey.Exponent);
BigInteger p = new BigInteger(ident.PrivateKey.P);
BigInteger q = new BigInteger(ident.PrivateKey.Q);
BigInteger dP = new BigInteger(ident.PrivateKey.DP);
BigInteger dQ = new BigInteger(ident.PrivateKey.DQ);
BigInteger qInv = new BigInteger(ident.PrivateKey.QInv);
RsaKeyParameters kp = new RsaPrivateCrtKeyParameters(modulus, publicExponent, privateExponent, p, q, dP, dQ, qInv);
AsymmetricKeyEntry privateKey = new AsymmetricKeyEntry(kp);
// Now let's convert to a Bouncy Castle cert and wrap it for packaging
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(dotNetCert);
X509CertificateEntry certEntry = new X509CertificateEntry(cert);
// Set the private key and certificate into the store
store.SetCertificateEntry(friendlyName, certEntry);
store.SetKeyEntry(ident.PrivateKeyName, privateKey, new X509CertificateEntry[] { certEntry });
}
using (MemoryStream ms = new MemoryStream())
{
store.Save(ms, password.ToCharArray(), new SecureRandom());
ms.Flush();
byte[] p12Bytes = ms.ToArray();
return p12Bytes;
}
}
正如我所说,这对于在 Windows 上导入非常有用,但在导入 Mac 钥匙串时会失败并出现非常普遍的错误。
在加载钥匙串生成的 p12 和我自己生成的 p12 文件时,我可以看到一个主要区别,但我不知道这是否是原因。
如果我将 Mac 钥匙串生成的 p12 加载到 Bouncy Castle PKCS12Store 中,然后检查钥匙串 p12 上的密钥,证书和私钥都有一个属性,其密钥为“1.2.840.113549.1.9.21”,具有等效值(值为 #af8a1d6891efeb32756c12b7bdd96b5ec673e11e 的 DerOctetString)。
如果我对生成的 p12 文件执行相同的操作,则私钥包含“1.2.840.113549.1.9.21”属性,但证书不包含。
如果我Google“1.2.840.113549.1.9.21”,我发现这个OID意味着PKCS_12_LOCAL_KEY_ID。我唯一的理论是钥匙串依赖它来匹配证书和私钥,而我生成的文件没有这个,所以它失败了。
但是,我尝试将这些值添加到哈希表中,然后使用采用属性哈希表的 CertificateEntry 构造函数。如果我这样做,然后保存字节,然后重新加载字节,该属性将再次丢失。
所以我很困惑。也许这个属性是 Bouncy Castle API 中的一个小故障?也许我做错了什么。也许钥匙串对传入的 p12 文件有荒谬的非标准要求。无论如何,如果您能提供任何帮助,我们将不胜感激。
Our organization manages a stable of iOS applications for multiple clients, which means dealing with a lot of different developer identity certificates and push notification certificates.
I have had success with the Bouncy Castle C# Crypto API in simplifying management of the certificates and private keys for push notifications, essentially eliminating the need for the Keychain for all our push notification certificates.
I would like to extend this to the developer identity certificates. The goal would be to store all the private key and certificate information in the database for each developer identity. Then when a new developer or build machine needs to be provisioned, server side code could wrap all of the certificates and private keys into one p12 archive with one password that could be imported into the target Mac's Keychain.
Unfortunately, the Mac Keychain doesn't like the p12 files I'm generating. This is annoying since I can successfully import these files into the Windows certificate manager just fine.
The code I'm using (the important parts) looks like this:
private byte[] GetP12Bytes(List<DevIdentity> identities, string password)
{
Pkcs12Store store = new Pkcs12Store();
foreach(DevIdentity ident in identities)
{
// Easiest to create a Bouncy Castle cert by converting from .NET
var dotNetCert = new X509Certificate2(ident.CertificateBytes);
// This method (not shown) parses the CN= attribute out of the cert's distinguished name
string friendlyName = GetFriendlyName(dotNetCert.Subject);
// Now reconstitute the private key from saved value strings
BigInteger modulus = new BigInteger(ident.PrivateKey.Modulus);
BigInteger publicExponent = new BigInteger(ident.PrivateKey.PublicExponent);
BigInteger privateExponent = new BigInteger(ident.PrivateKey.Exponent);
BigInteger p = new BigInteger(ident.PrivateKey.P);
BigInteger q = new BigInteger(ident.PrivateKey.Q);
BigInteger dP = new BigInteger(ident.PrivateKey.DP);
BigInteger dQ = new BigInteger(ident.PrivateKey.DQ);
BigInteger qInv = new BigInteger(ident.PrivateKey.QInv);
RsaKeyParameters kp = new RsaPrivateCrtKeyParameters(modulus, publicExponent, privateExponent, p, q, dP, dQ, qInv);
AsymmetricKeyEntry privateKey = new AsymmetricKeyEntry(kp);
// Now let's convert to a Bouncy Castle cert and wrap it for packaging
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(dotNetCert);
X509CertificateEntry certEntry = new X509CertificateEntry(cert);
// Set the private key and certificate into the store
store.SetCertificateEntry(friendlyName, certEntry);
store.SetKeyEntry(ident.PrivateKeyName, privateKey, new X509CertificateEntry[] { certEntry });
}
using (MemoryStream ms = new MemoryStream())
{
store.Save(ms, password.ToCharArray(), new SecureRandom());
ms.Flush();
byte[] p12Bytes = ms.ToArray();
return p12Bytes;
}
}
Like I said, this works great for import on Windows, but fails with a very generic error when importing into the Mac Keychain.
There is one major difference I can see when loading a Keychain-generated p12 and my own generated p12 file, but I do not know if this is the cause.
If I load the Mac Keychain generated p12 into a Bouncy Castle PKCS12Store, and then examine the keys, on the Keychain p12, both the certificate and the private key have an attribute with the key "1.2.840.113549.1.9.21" with equivalent values (a DerOctetString with value #af8a1d6891efeb32756c12b7bdd96b5ec673e11e).
If I do the same to my generated p12 file, the private key contains the "1.2.840.113549.1.9.21" attribute, but the Certificate does not.
If I Google "1.2.840.113549.1.9.21", I find out that this OID means PKCS_12_LOCAL_KEY_ID . My only theory is that the Keychain relies on this to match up the certificate and private key, and that my generated file does not have this, so it fails.
However, I've tried adding these values to a Hashtable and then using the CertificateEntry constructor that takes the attribute hashtable. If I do that, and then save the bytes, and then reload the bytes, that attribute is again missing.
So I'm flummoxed. Maybe this attribute is a glitch in the Bouncy Castle API? Maybe there's something I'm doing wrong. Maybe the Keychain has ridiculous non-standard requirements for incoming p12 files. In any case, any help that could be provided would be greatly appreciated.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
BouncyCastle 的 Pkcs12Store 负责为您设置友好名称和本地密钥 ID 属性(或者至少在 2011 年 4 月左右的 1.7 版本中是这样做的)。我的猜测是您一定使用了旧版本,而该版本不起作用。
以下是我将 iPhone 开发人员身份保存到 Pkcs12Store 实例的方法(省略了额外的内容和安全性):
在 OS X 10.7 上的 Keychain Access.app 中导入商店,正确地将证书和私钥放入钥匙串中,并将证书放入UI 中的私钥,就像 Keychain Access 本身生成的证书和密钥一样。
附带说明一下,Pkcs12Store 似乎使用证书的公钥来生成证书和密钥条目共享的 LocalKeyId 属性的值。
您可以在此处查看 Pkcs12Store 源代码的相关部分。
BouncyCastle's Pkcs12Store takes care of setting both the Friendly Name and Local Key ID attributes for you (or at least it does so in the 1.7 release, circa April 2011). My guess is that you must have used an older version where this didn't work.
Here's how I'm saving an iPhone Developer identity to a Pkcs12Store instance (extra stuff and security omitted):
Importing the store in Keychain Access.app on OS X 10.7 correctly places the certificate and private key in the keychain and places the certificate within the private key in the UI, as with a certificate and key generated by Keychain Access itself.
On a side note, it seems that Pkcs12Store uses the public key of the certificate to generate the value of the LocalKeyId attribute shared by the certificate and key entries.
You can see the relevant section of the Pkcs12Store source here.