我想要达到的目标基本上归结为一个问题:
必须根据安全配置存储中的数据在内存中创建信任库,而不是从文件加载信任库。
事实证明这有点棘手,但绝对有可能。
创建信任库很容易:
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
ts.load(null);
for (Certificate cert : certList) {
ts.setCertificateEntry(UUID.randomUUID().toString(), cert);
}
但是,将其提供给 SSL 处理管道有点棘手。基本上,我们需要做的是提供一个使用我们上面创建的信任库的X509ExtendedTrustManager 的实现。
为了让 SSL 处理管道知道这个实现,我们需要实现我们自己的提供者:
public class ReloadableTrustManagerProvider extends Provider {
public ReloadableTrustManagerProvider() {
super("ReloadableTrustManager", 1, "Provider to load client certificates from memory");
put("TrustManagerFactory." + TrustManagerFactory.getDefaultAlgorithm(), ReloadableTrustManagerFactory.class.getName());
}
}
此提供程序又使用TrustManagerFactorySpi 实现:
public class ReloadableTrustManagerFactory extends TrustManagerFactorySpi {
private final TrustManagerFactory originalTrustManagerFactory;
public ReloadableTrustManagerFactory() throws NoSuchAlgorithmException {
ProviderList originalProviders = ProviderList.newList(
Arrays.stream(Security.getProviders()).filter(p -> p.getClass() != ReloadableTrustManagerProvider.class)
.toArray(Provider[]::new));
Provider.Service service = originalProviders.getService("TrustManagerFactory", TrustManagerFactory.getDefaultAlgorithm());
originalTrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm(), service.getProvider());
}
@Override
protected void engineInit(KeyStore keyStore) throws KeyStoreException {
}
@Override
protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws InvalidAlgorithmParameterException {
}
@Override
protected TrustManager[] engineGetTrustManagers() {
try {
return new TrustManager[]{new ReloadableX509TrustManager(originalTrustManagerFactory)};
} catch (Exception e) {
return new TrustManager[0];
}
}
}
稍后会详细了解originalTrustManagerFactory 和ReloadableX509TrustManager。
最后,我们需要以一种使其成为默认提供程序的方式注册提供程序,以便 SSL 管道将使用它:
Security.insertProviderAt(new ReloadableTrustManagerProvider(), 1);
这段代码可以在main中执行,在SpringApplication.run之前。
回顾一下:我们需要将我们的提供者插入到安全提供者列表中。我们的提供者使用我们自己的信任管理器工厂来创建我们自己的信任管理器的实例。
仍然缺少两件事:
- 我们的信任管理器的实现
-
originalTrustManagerFactory的解释
一、实现(基于https://donneyfan.com/blog/dynamic-java-truststore-for-a-jax-ws-client):
public class ReloadableX509TrustManager extends X509ExtendedTrustManager implements X509TrustManager {
private final TrustManagerFactory originalTrustManagerFactory;
private X509ExtendedTrustManager clientCertsTrustManager;
private X509ExtendedTrustManager serverCertsTrustManager;
private ArrayList<Certificate> certList;
private static Log logger = LogFactory.getLog(ReloadableX509TrustManager.class);
public ReloadableX509TrustManager(TrustManagerFactory originalTrustManagerFactory) throws Exception {
try {
this.originalTrustManagerFactory = originalTrustManagerFactory;
certList = new ArrayList<>();
/* Example on how to load and add a certificate. Instead of loading it here, it should be loaded externally and added via addCertificates
// Should get from secure configuration store
String cert64 = "base64 encoded certificate";
byte encodedCert[] = Base64.getDecoder().decode(cert64);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
certList.add(cert); */
reloadTrustManager();
} catch (Exception e) {
logger.fatal(e);
throw e;
}
}
/**
* Removes a certificate from the pending list. Automatically reloads the TrustManager
*
* @param cert is not null and was already added
* @throws Exception if cannot be reloaded
*/
public void removeCertificate(Certificate cert) throws Exception {
certList.remove(cert);
reloadTrustManager();
}
/**
* Adds a list of certificates to the manager. Automatically reloads the TrustManager
*
* @param certs is not null
* @throws Exception if cannot be reloaded
*/
public void addCertificates(List<Certificate> certs) throws Exception {
certList.addAll(certs);
reloadTrustManager();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
clientCertsTrustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
clientCertsTrustManager.checkClientTrusted(x509Certificates, s, socket);
}
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
clientCertsTrustManager.checkClientTrusted(x509Certificates, s, sslEngine);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
serverCertsTrustManager.checkServerTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
serverCertsTrustManager.checkServerTrusted(x509Certificates, s, socket);
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
serverCertsTrustManager.checkServerTrusted(x509Certificates, s, sslEngine);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return ArrayUtils.addAll(serverCertsTrustManager.getAcceptedIssuers(), clientCertsTrustManager.getAcceptedIssuers());
}
private void reloadTrustManager() throws Exception {
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
ts.load(null);
for (Certificate cert : certList) {
ts.setCertificateEntry(UUID.randomUUID().toString(), cert);
}
clientCertsTrustManager = getTrustManager(ts);
serverCertsTrustManager = getTrustManager(null);
}
private X509ExtendedTrustManager getTrustManager(KeyStore ts) throws NoSuchAlgorithmException, KeyStoreException {
originalTrustManagerFactory.init(ts);
TrustManager tms[] = originalTrustManagerFactory.getTrustManagers();
for (int i = 0; i < tms.length; i++) {
if (tms[i] instanceof X509ExtendedTrustManager) {
return (X509ExtendedTrustManager) tms[i];
}
}
throw new NoSuchAlgorithmException("No X509TrustManager in TrustManagerFactory");
}
}
这个实现有几个值得注意的点:
- 它实际上将所有工作委托给正常的默认信任管理器。为了能够获得它,我们需要拥有 SSL 管道通常使用的默认信任管理器工厂。这就是我们在构造函数中将其作为参数
originalTrustManagerFactory 传递的原因。
- 我们实际上使用了两种不同的信任管理器实例:一种用于验证客户端证书 - 当客户端向我们发送请求并使用客户端证书对自身进行身份验证时使用它 - 另一个用于验证服务器证书 - 在以下情况下使用我们使用 HTTPS 向服务器发送请求。为了验证客户端证书,我们使用我们自己的信任库创建了一个信任管理器。这将仅包含存储在我们的安全配置存储中的证书,因此它不会包含 Java 通常信任的任何根 CA。如果我们将这个信任管理器用于对我们作为客户端的 HTTPS URL 的请求,则请求将失败,因为我们将无法验证服务器证书的有效性。因此,服务器证书验证的信任管理器是在不传递信任库的情况下创建的,因此将使用默认的 Java 信任库。
-
getAcceptedIssuers 需要从我们的信任管理器返回接受的颁发者,因为在这种方法中,我们不知道证书验证是针对客户端证书还是服务器证书进行。这有一个小缺点,我们的信任管理器也会信任使用我们的自签名客户端证书进行 HTTPS 的服务器。
要完成所有这些工作,我们需要启用 ssl 客户端身份验证:
server.ssl.key-store: classpath:keyStore.p12 # secures our API with SSL. Needed, to enable client certificates handling
server.ssl.key-store-password: very-secure
server.ssl.client-auth: need
因为我们正在创建自己的信任库,所以我们不需要设置 server.ssl.trust-store 及其相关设置