![](/img/trans.png)
[英]How java SSLSocketFactory choose the server certificate and private key during SSL from keystore
[英]How to build a SSLSocketFactory from PEM certificate and key without converting to keystore?
我得到了一個自簽名的客戶端證書工具包,用於通過 HTTPS 訪問服務器。 該套件包含以下 PEM 文件:
解決該任務的一種方法是生成 Java 密鑰庫:
...然后使用如下代碼構建SSLSocketFactory實例:
InputStream stream = new ByteArrayInputStream(pksData);
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(stream, password);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, password.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();
TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmfactory.init(keyStore);
TrustManager[] trustManagers = tmfactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
sslSocketFactory = sslContext.getSocketFactory();
... 后來用於初始化 http 庫。
所以我們獲得了一個 KeyStore,然后在它的幫助下初始化 KeyManagers 和 TrustManagers,最后我們用它們構建 SSLSocketFactory 實例。
問題是:有沒有辦法避免創建密鑰庫文件並以某種方式構建 SSLSocketFactory 從 PublicKey 和 Certificate 實例開始(例如,可以使用 bouncycastle 的 PemReader 從 PEM 文件中獲取)?
結果證明,仍然需要構建一個 KeyStore 實例,但它可以在內存中完成(從 PEM 文件作為輸入開始),而無需使用 keytool 構建中間密鑰庫文件。
要構建內存中的 KeyStore,可以使用如下代碼:
private static final String TEMPORARY_KEY_PASSWORD = "changeit";
private KeyStore getKeyStore() throws ConfigurationException {
try {
Certificate clientCertificate = loadCertificate(certificatePem);
PrivateKey privateKey = loadPrivateKey(privateKeyPem);
Certificate caCertificate = loadCertificate(caPem);
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
keyStore.setCertificateEntry("ca-cert", caCertificate);
keyStore.setCertificateEntry("client-cert", clientCertificate);
keyStore.setKeyEntry("client-key", privateKey, TEMPORARY_KEY_PASSWORD.toCharArray(), new Certificate[]{clientCertificate});
return keyStore;
} catch (GeneralSecurityException | IOException e) {
throw new ConfigurationException("Cannot build keystore", e);
}
}
private Certificate loadCertificate(String certificatePem) throws IOException, GeneralSecurityException {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
final byte[] content = readPemContent(certificatePem);
return certificateFactory.generateCertificate(new ByteArrayInputStream(content));
}
private PrivateKey loadPrivateKey(String privateKeyPem) throws IOException, GeneralSecurityException {
return pemLoadPrivateKeyPkcs1OrPkcs8Encoded(privateKeyPem);
}
private byte[] readPemContent(String pem) throws IOException {
final byte[] content;
try (PemReader pemReader = new PemReader(new StringReader(pem))) {
final PemObject pemObject = pemReader.readPemObject();
content = pemObject.getContent();
}
return content;
}
private static PrivateKey pemLoadPrivateKeyPkcs1OrPkcs8Encoded(
String privateKeyPem) throws GeneralSecurityException, IOException {
// PKCS#8 format
final String PEM_PRIVATE_START = "-----BEGIN PRIVATE KEY-----";
final String PEM_PRIVATE_END = "-----END PRIVATE KEY-----";
// PKCS#1 format
final String PEM_RSA_PRIVATE_START = "-----BEGIN RSA PRIVATE KEY-----";
final String PEM_RSA_PRIVATE_END = "-----END RSA PRIVATE KEY-----";
if (privateKeyPem.contains(PEM_PRIVATE_START)) { // PKCS#8 format
privateKeyPem = privateKeyPem.replace(PEM_PRIVATE_START, "").replace(PEM_PRIVATE_END, "");
privateKeyPem = privateKeyPem.replaceAll("\\s", "");
byte[] pkcs8EncodedKey = Base64.getDecoder().decode(privateKeyPem);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
} else if (privateKeyPem.contains(PEM_RSA_PRIVATE_START)) { // PKCS#1 format
privateKeyPem = privateKeyPem.replace(PEM_RSA_PRIVATE_START, "").replace(PEM_RSA_PRIVATE_END, "");
privateKeyPem = privateKeyPem.replaceAll("\\s", "");
DerInputStream derReader = new DerInputStream(Base64.getDecoder().decode(privateKeyPem));
DerValue[] seq = derReader.getSequence(0);
if (seq.length < 9) {
throw new GeneralSecurityException("Could not parse a PKCS1 private key.");
}
// skip version seq[0];
BigInteger modulus = seq[1].getBigInteger();
BigInteger publicExp = seq[2].getBigInteger();
BigInteger privateExp = seq[3].getBigInteger();
BigInteger prime1 = seq[4].getBigInteger();
BigInteger prime2 = seq[5].getBigInteger();
BigInteger exp1 = seq[6].getBigInteger();
BigInteger exp2 = seq[7].getBigInteger();
BigInteger crtCoef = seq[8].getBigInteger();
RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2,
exp1, exp2, crtCoef);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(keySpec);
}
throw new GeneralSecurityException("Not supported format of a private key");
}
該想法取自以編程方式從 PEM 獲取密鑰庫
當我面臨類似的挑戰時,我早些時候對您的回答發表了評論,現在我回來提供加載 pem 文件的替代方法。 我已經用它創建了一個庫,以方便我自己和其他人使用,請參見此處: GitHub - SSLContext Kickstart我希望你喜歡它 :)
添加以下依賴項:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-pem</artifactId>
<version>6.8.0</version>
</dependency>
可以使用以下代碼段加載 pem 文件:
var keyManager = PemUtils.loadIdentityMaterial("certificate-chain.pem", "private-key.pem");
var trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");
var sslFactory = SSLFactory.builder()
.withIdentityMaterial(keyManager)
.withTrustMaterial(trustManager)
.build();
var sslContext = sslFactory.getSslContext();
var sslSocketFactory = sslFactory.getSslSocketFactory();
回到您的主要問題,我還發現無法在沒有 KeyStore 的情況下創建 SSLSocketFactory。 並且內存中的 KeyStore 可以完美地按照您對此用例的建議進行工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.