我正在尝试使用 TLS 连接到 Redis,对于内部只有一个证书的密钥库来说它工作得很好。
问题是,如果我将多个证书导入到我的密钥库中,它如何知道选择正确的别名来提取正确的密钥?
我实现了自己的 X509KeyManager 来看看它是如何工作的,以及
chooseClientAlias(String[] strings, Principal[] prncpls, Socket socket)
方法似乎传递了一个 prncples 的空数组,我想这就是它如何知道要使用哪个证书的方法。
但由于它是空的,它只是返回与字符串输入中指定的密钥类型(又名 RSA)匹配的第一个别名,并且第一个别名可能不是正确的(最终会选择错误的密钥,并且 ssl 连接失败)。
我是否误解了如何为连接选择正确的别名,例如我是否需要为我接口的每个 SSL 应用程序创建不同的 SSL 套接字工厂和 KeyManager,并显式指定别名使用?抱歉,我不太熟悉 TLS 和 java。谢谢。
我用来生成证书的命令(运行两次以创建真正的测试证书,以及我在真实证书之后导入的随机假证书,以测试它是否会选择正确的别名):
Create CA:
===
"C:\Program Files\Git\mingw64\bin\openssl.exe" genrsa -out ca.key 2048
"C:\Program Files\Git\mingw64\bin\openssl.exe" req -new -x509 -sha256 -key ca.key -out ca.crt
Create Redis Server Cert:
===
"C:\Program Files\Git\mingw64\bin\openssl.exe" genrsa -out redis.key
"C:\Program Files\Git\mingw64\bin\openssl.exe" req -new -sha256 -key redis.key -out redis.csr
"C:\Program Files\Git\mingw64\bin\openssl.exe" x509 -req -in redis.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out redis.crt -days 1000 -sha256
Create Client:
===
"C:\Program Files\Git\mingw64\bin\openssl.exe" genrsa -out client1.key 2048
"C:\Program Files\Git\mingw64\bin\openssl.exe" req -new -sha256 -key client1.key -out client1.csr
"C:\Program Files\Git\mingw64\bin\openssl.exe" x509 -req -in client1.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client1.crt -days 1000 -sha256
我用来将证书导入密钥库的命令:
Add ca to truststore:
=====
keytool -import -alias redisCA -keystore keystore.jks -file ca.crt
generate pkcs12:
=====
openssl pkcs12 -export -in client1.crt -inkey client1.key -out keystore.p12 -name my_cert
Import pkcs12 cert/key to keystore:
=====
keytool -importkeystore -destkeystore keystore.jks -srckeystore keystore.p12 -srcstoretype PKCS12 -alias my_cert
我用来与 Redis 交互的代码(基本上直接从他们的网站示例中获取):
public void testWithTls() throws IOException, GeneralSecurityException {
HostAndPort address = new HostAndPort("localhost", 6379);
SSLSocketFactory sslFactory = createSslSocketFactory(
"D:\\tmp\\keystore.jks",
"123456",
"D:\\tmp\\keystore.jks",
"123456"
);
JedisClientConfig config = DefaultJedisClientConfig.builder()
.ssl(true).sslSocketFactory(sslFactory)
.build();
JedisPooled jedis = new JedisPooled(address, config);
jedis.set("foo", "bar");
System.out.println(jedis.get("foo")); // prints bar
}
private static SSLSocketFactory createSslSocketFactory(
String caCertPath, String caCertPassword, String userCertPath, String userCertPassword)
throws IOException, GeneralSecurityException {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(userCertPath), userCertPassword.toCharArray());
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(new FileInputStream(caCertPath), caCertPassword.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(trustStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, userCertPassword.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
信息:
Jedis version: 4.4.3
Redis Docker container version: redis:7.0.10
Redis Docker container run command: `redis-server --tls-port 6379 --port 0 --tls-cert-file /tls/redis.crt --tls-key-file /tls/redis.key --tls-ca-cert-file /tls/ca.crt --loglevel warning`
Why am I using a jks store and not the p12: Because thats what the company I work at uses
JDK 中没有针对此类用例的内置解决方案。我建议创建 2 个 SSLContext。对于每个密钥库 1 并使用它来调用 Jedis 服务器。
如果您愿意使用图书馆,那么这个选项是可以的。我创建了这种可以解决问题的选项。请参阅下面的代码片段:
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.CertificateUtils;
import javax.net.ssl.SSLSocketFactory;
import java.nio.file.Paths;
import java.security.cert.Certificate;
import java.util.List;
public class App {
public static void main(String[] args) {
String caCertPath = "D:\\tmp\\ca-certs.crt";
List<Certificate> certificates = CertificateUtils.loadCertificate(Paths.get(caCertPath));
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(Paths.get("D:\\tmp\\keystore-one.jks"), "123456".toCharArray())
.withIdentityMaterial(Paths.get("D:\\tmp\\keystore-two.jks"), "123456".toCharArray())
.withIdentityRoute("client-alias-one", "https://localhost:6379/", "https://localhost:6380/")
.withIdentityRoute("client-alias-two", "https://localhost:6381/", "https://localhost:6382/")
.withTrustMaterial(certificates)
.build();
SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();
JedisClientConfig config = DefaultJedisClientConfig.builder()
.ssl(true)
.sslSocketFactory(sslSocketFactory)
.build();
}
}
在这种情况下,您需要指定在密钥库文件中为密钥命名的别名,并将其映射到特定主机和端口作为身份路由(也称为密钥材料路由)。这样,它将使用目标服务器列表的特定密钥。该库可以在这里找到:sslcontext-kickstart