好的,起初听起来很奇怪,所以请忍受我:-)
我需要解决的问题是:
我需要以某种方式在Spring Boot应用程序中启用客户端身份验证,该方法允许客户端自己创建证书,而无需服务器使用服务器私钥对CSR进行签名。 >
我如何实现这个目标?
背景:为什么我需要这个?
我们已经设置了一个Spring Cloud Config Server。它包含许多不同应用程序的配置值。现在,我们只允许每个应用程序访问其自己的配置值。
解决此问题的最简单但安全的方法似乎是:
第7点将作为简单的Filter
实现。
答案 0 :(得分:1)
我想要实现的目标基本上可以归结为一个问题:
代替从文件加载信任库,必须基于安全配置存储中的数据在内存中创建信任库。
事实证明这有点棘手,但绝对有可能。
创建信任库很容易:
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");
}
}
此实现有一些值得注意的要点:
originalTrustManagerFactory
传递的原因。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
及其相关设置