来自 CA 的 PKCS12 Java Keystore 和 Java 中的用户证书
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3614239/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
PKCS12 Java Keystore from CA and User certificate in java
提问by Staros
I've recently been put in charge of mocking up an Apple product (iPhone Configuration Utility) in Java. One of the sections I've been a bit stuck on is a part about Exchange ActiveSync. In there, it allows you to select a certificate from your Keychain to use as credentials for your EAS account. After some research, I found that it's actually creating a PKCS12 keystore, inserting the private key of the certificate I selected, and encoding that into XML. So far not a big deal. If I create a .p12 file with Keychain Access it uploads without a problem. But I run into a problem when I try to bring that over to Java.
我最近负责用 Java 模拟 Apple 产品(iPhone 配置实用程序)。我有点坚持的部分之一是关于 Exchange ActiveSync 的部分。在那里,它允许您从您的钥匙串中选择一个证书以用作您的 EAS 帐户的凭据。经过一番研究,我发现它实际上是在创建一个 PKCS12 密钥库,插入我选择的证书的私钥,并将其编码为 XML。到目前为止没什么大不了的。如果我使用 Keychain Access 创建一个 .p12 文件,它可以毫无问题地上传。但是当我尝试将其引入 Java 时遇到了问题。
Say I export one of those certs that I had used earlier with the .p12 file as a .cer file (this is what we are expecting to get in the environment). Now when I upload it into Java I get a Certificate object as follows...
假设我将之前与 .p12 文件一起使用的那些证书之一导出为 .cer 文件(这是我们期望在环境中获得的)。现在,当我将它上传到 Java 时,我得到一个证书对象,如下所示......
KeyStore ks = java.security.KeyStore.getInstance("PKCS12");
ks.load(null, "somePassword".toCharArray());
CertificateFactory cf = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());
java.security.cert.Certificate userCert = cf.generateCertificate(new FileInputStream("/Users/me/Desktop/RecentlyExportedCert.cer"));
But when I try...
但是当我尝试...
ks.setCertificateEntry("SomeAlias", userCert);
I get the exception...
我得到了例外...
java.security.KeyStoreException: TrustedCertEntry not supported
So from certs I move onto keys. But with those Certificates (I got the CA Cert as well), I'm only able to access the public key, not the private. And if I attempt to add the public key like so...
所以从证书我转向钥匙。但是有了这些证书(我也获得了 CA 证书),我只能访问公钥,而不能访问私钥。如果我尝试像这样添加公钥......
java.security.cert.Certificate[] chain = {CACert};
ks.setKeyEntry("SomeAlias", userCert.getPublicKey().getEncoded(), chain);
I get...
我得到...
java.security.KeyStoreException: Private key is not stored as PKCS#8 EncryptedPrivateKeyInfo: java.io.IOException: DerValue.getOctetString, not an Octet String: 3
So now I'm here. Does anyone have any idea how to get a private key from a .cer file into a PKCS12 keystore in Java? Am I even on the right track?
所以现在我来了。有没有人知道如何将私钥从 .cer 文件获取到 Java 中的 PKCS12 密钥库?我是否在正确的轨道上?
Thanks in advance!
提前致谢!
采纳答案by Bruno
The PKCS#12 format is intended for storing a private key associated with a certificate chain, and both are required (although you might not need the whole chain).
Although the PKCS12
keystore type does a good job for mapping this format to a Java KeyStore
, not everything is supported for this reason.
PKCS#12 格式用于存储与证书链关联的私钥,两者都是必需的(尽管您可能不需要整个链)。尽管PKCS12
keystore 类型在将此格式映射到 Java 方面做得很好KeyStore
,但由于这个原因,并非所有东西都受支持。
What you're trying to do in your first attempt is storing a certificate on its own, which won't work.
您在第一次尝试时尝试做的是自行存储证书,这是行不通的。
What you're trying to do in your second attempt (ks.setKeyEntry("SomeAlias", userCert.getPublicKey().getEncoded(), chain)
) is to for a public key in place of what should be a private key (see KeyStore#setKeyEntry
).
您在第二次尝试 ( ks.setKeyEntry("SomeAlias", userCert.getPublicKey().getEncoded(), chain)
) 中尝试做的是使用公钥代替应该是私钥的内容(请参阅 参考资料KeyStore#setKeyEntry
)。
.cer
file tend to be just for certificates not private keys (although of course, the extension is ultimately just an indication). If you export your .cer
file from Keychain Access.app, you won't get the private key with it (that's what the .p12
export format is for).
.cer
file 往往仅用于证书而不是私钥(当然,扩展名最终只是一个指示)。如果您.cer
从Keychain Access.app导出文件,您将不会获得私钥(这就是.p12
导出格式的用途)。
EDITabout KeychainStore:
关于 KeychainStore 的编辑:
If the reason you're trying to do this conversion is ultimately to access private keys and certificates that are already in the keychain you could load them from the KeychainStore
directly:
如果您尝试进行此转换的原因最终是为了访问钥匙串中已有的私钥和证书,您可以KeychainStore
直接从以下位置加载它们:
KeyStore ks = KeyStore.getInstance("KeychainStore", "Apple");
ks.load(null, "-".toCharArray());
A couple of notes for this:
一些注意事项:
- Any non-null, non-empty password will do to use the private key (e.g.
"-".toCharArray()
), as access will be prompted by the OS's security service (like it would in other applications). - As far as I'm aware, there is still a bug and it only allows access to one private key/certificate pair(even if a number of pairs of private key/certificate pairs are present in the keychain)
- 任何非空、非空密码都可以使用私钥(例如
"-".toCharArray()
),因为操作系统的安全服务会提示访问(就像在其他应用程序中一样)。 - 据我所知,仍然存在一个错误,它只允许访问一个私钥/证书对(即使钥匙串中存在多对私钥/证书对)
回答by Fico
http://www.docjar.com/html/api/org/bouncycastle/jce/examples/PKCS12Example.java.html
http://www.docjar.com/html/api/org/bouncycastle/jce/examples/PKCS12Example.java.html
This is how to add a certificate with a associating private key to a PKCS12 keystore. When you are using client authentication, the keystore needs to contain also the private key, in that case you use KeyStore.getInstance("PKCS12").
这是将带有关联私钥的证书添加到 PKCS12 密钥库的方法。当您使用客户端身份验证时,密钥库还需要包含私钥,在这种情况下,您可以使用 KeyStore.getInstance("PKCS12")。
When youre not using client authentication but only server authentication(and private key will not be added to keystore, since it belongs to the server), its better to use KeyStore.getInstance("JKS"), than you can add multiple certificates with an alias to that one keystore.
当您不使用客户端身份验证而只使用服务器身份验证(并且私钥不会添加到密钥库,因为它属于服务器)时,最好使用 KeyStore.getInstance("JKS"),而不是您可以添加多个证书该密钥库的别名。
When you are using PKCS12, as far as I know, you can only add 1 certificate(you have to add the whole certificate chain) associated with the private key, you want to use for that certificate.
当您使用 PKCS12 时,据我所知,您只能添加与要用于该证书的私钥关联的 1 个证书(您必须添加整个证书链)。
回答by markdsievers
I'm a couple years late to the party but this took me a few hours to get working correctly,
so I thought it was worth posting a working solution.
This solution uses 1) A .p12 / PKCS12 certificate
and 2) a CA not in the default TrustManager (and you want to add it programatically rather than adding to the default TrustManager). 3) No third party cryptography libraries, just HttpClient
to bring it all together.
我参加聚会晚了几年,但这花了我几个小时才能正常工作,所以我认为值得发布一个有效的解决方案。此解决方案使用 1) .p12 / PKCS12 证书和 2) 不在默认 TrustManager 中的 CA(并且您希望以编程方式添加它而不是添加到默认 TrustManager)。3) 没有第三方密码学库,只是HttpClient
将它们整合在一起。
I've also added a few helpful keytool
and openssl
commands in the JavaDoc for working with certificates, as that is an art in itself.
我还添加了一些有用的keytool
,并openssl
在JavaDoc命令用于证书的,因为这本身就是一种艺术。
// Stitch it all together with HttpClient
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(getSSLSocketFactory()).build();
private SSLConnectionSocketFactory getSSLSocketFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyManager[] keyManager = getKeyManager("pkcs12", "path/to/cert.p12"), "p12_password"));
TrustManager[] trustManager = getTrustManager("jks", "path/to/CA.truststore", "trust_store_password"));
sslContext.init(keyManager, trustManager, new SecureRandom());
return new SSLConnectionSocketFactory(sslContext);
} catch (Exception e) {
throw new RuntimeException("Unable to setup keystore and truststore", e);
}
}
/**
* Some useful commands for looking at the client certificate and private key:
* keytool -keystore certificate.p12 -list -storetype pkcs12 -v
* openssl pkcs12 -info -in certificate.p12
*/
private KeyManager[] getKeyManager(String keyStoreType, String keyStoreFile, String keyStorePassword) throws Exception {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyStore.load(this.getClass().getClassLoader().getResourceAsStream(keyStoreFile), keyStorePassword.toCharArray());
kmf.init(keyStore, keyStorePassword.toCharArray());
return kmf.getKeyManagers();
}
/**
* Depending on what format (pem / cer / p12) you have received the CA in, you will need to use a combination of openssl and keytool
* to convert it to JKS format in order to be loaded into the truststore using the method below.
*
* You could of course use keytool to import this into the JREs TrustStore - my situation mandated I create it on the fly.
*
* Useful command to look at the CA certificate:
* keytool -keystore root_ca.truststore -list -storetype jks -v
*
*/
private TrustManager[] getTrustManager(String trustStoreType, String trustStoreFile, String trustStorePassword) throws Exception {
KeyStore trustStore = KeyStore.getInstance(trustStoreType);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustStore.load(this.getClass().getClassLoader().getResourceAsStream(trustStoreFile), trustStorePassword.toCharArray());
tmf.init(trustStore);
return tmf.getTrustManagers();
}