我想动态添加/替换 SSL 证书到我的 Spring Boot (tomcat) 应用程序,而不需要重新启动它。我还有很长的路要走,但目前我遇到了 javax.crypto.BadPaddingException 并且不知道为什么。
这就是我正在尝试做的事情。
首先,我定义自己的 TomcatServletWebServerFactory
以设置 SslStoreProvider
。
@Component
public class PathWatchingTomcatFactory extends TomcatServletWebServerFactory {
public PathWatchingTomcatFactory(PathWatchingSslStoreProvider pathWatchingSslStoreProvider) {
setSslStoreProvider(pathWatchingSslStoreProvider);
}
}
我的PathWatchingSslStoreProvider
提供了PathMatchingKeyStore
。
@Component
public class PathWatchingSslStoreProvider implements SslStoreProvider {
private final PathWatchingKeyStore pathWatchingKeyStore;
public PathWatchingSslStoreProvider(PathWatchingKeyStore pathWatchingKeyStore) {
this.pathWatchingKeyStore = pathWatchingKeyStore;
}
@Override
public KeyStore getKeyStore() throws Exception {
return pathWatchingKeyStore;
}
}
PathWatchingKeyStore
似乎只是为了向其提供服务提供者接口(interface)而必需的。
@Component
public class PathWatchingKeyStore extends KeyStore {
protected PathWatchingKeyStore(
PathWatchingKeyStoreSpi pathWatchingKeyStoreSpi,
DynamicProvider provider)
{
super(pathWatchingKeyStoreSpi, provider, KeyStore.getDefaultType());
initialize();
}
private void initialize() {
// Loading a keystore marks it internally as initialized and only
// initialized keystores work properly. Unfortunately
// nobody initializes this keystore. So we have to do it
// ourselves.
//
// Internally the keystore will delegate loading to the
// KeyStoreSpi, which, in our case is the PathWatchingKeyStoreSpi.
try {
load(null, null);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
现在,在启动时,将加载 keystore 。因为我提供了 SslStoreProvider,所以 SslStoreProviderUrlStreamHandlerFactory 将通过请求 PathWatchingKeyStoreSpi 将其 key 存储存储到 ByteArrayOutputStream 中来加载我的 key 存储,该 ByteArrayOutputStream 的内容最终会复制到用于加载内部使用的 key 存储的 InputStream 中。
在下面的代码片段中,您可以看到我如何尝试写入现有 keystore 的内容。现在一点动态都没有。我只想看看 Spring Boot 应用程序是否在所有这些自定义类就位的情况下启动。但事实并非如此。
@Component
public class PathWatchingKeyStoreSpi extends KeyStoreSpi {
private static final Logger LOGGER = LoggerFactory.getLogger(PathWatchingKeyStoreSpi.class);
private final Path keyStoreLocation;
public PathWatchingKeyStoreSpi(@Value("${server.ssl.key-store}") Path keyStoreLocation) {
super();
this.keyStoreLocation = keyStoreLocation;
}
@Override
public void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
try {
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(keyStoreLocation.toString()), "secret".toCharArray());
// Password must be empty because the SslConnectorCustomizer sets the keystore
// password used by the tomcat to the empty string if the SslStoreProvider
// returns a keystore. And because that is what we wanted to do in the first place,
// providing a dynamic keystore, this is what we have to do.
keyStore.store(stream, "".toCharArray());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
我可以看到 keystore 已加载,但一旦 SSLUtilBase 尝试从该存储中读取 key ,它就会抛出 BadPaddingException:
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975) ~[na:na]
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056) ~[na:na]
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853) ~[na:na]
at java.base/com.sun.crypto.provider.PKCS12PBECipherCore.implDoFinal(PKCS12PBECipherCore.java:408) ~[na:na]
at java.base/com.sun.crypto.provider.PKCS12PBECipherCore$PBEWithSHA1AndDESede.engineDoFinal(PKCS12PBECipherCore.java:440) ~[na:na]
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202) ~[na:na]
at java.base/sun.security.pkcs12.PKCS12KeyStore.lambda$engineGetKey$0(PKCS12KeyStore.java:406) ~[na:na]
at java.base/sun.security.pkcs12.PKCS12KeyStore$RetryWithZero.run(PKCS12KeyStore.java:302) ~[na:na]
at java.base/sun.security.pkcs12.PKCS12KeyStore.engineGetKey(PKCS12KeyStore.java:400) ~[na:na]
... 25 common frames omitted
我创建了我在这里使用的静态 keystore ,如下所示:
keytool -genkey -alias tomcat -keyalg RSA
首先,我要解决问题的方向有前途吗?还是我完全错了?我首先尝试只注入(inject)我自己的 X509ExtendedKeyManager
。我可以在调试器中看到, key 管理器需要为传入请求提供证书,但尽管如此,tomcat 端点似乎是使用 keystore 进行初始化的,而没有涉及管理器。
有没有人尝试过使用 tomcat 作为servlt容器为Spring Boot应用程序实现和使用动态 keystore /trustore?
欢迎任何帮助。 托比亚斯
最佳答案
好吧,我不知道这是否是最终的解决方案,但现在它似乎比我上面描述的第一种方法更有前途(而且不太复杂)。
一切都从 TomcatServletWebServerFactory 开始。但这次我设置了一个全新的 JSSEImplementation:
@Component
public class PathWatchingTomcatFactory extends TomcatServletWebServerFactory {
private final Path keysLocation;
public PathWatchingTomcatFactory(@Value("${tobias.spring.ssl.keys-location}")Path keysLocation) {
this.keysLocation = requireNonNull(keysLocation);
}
@Override
protected void customizeConnector(Connector connector) {
super.customizeConnector(connector);
connector.setProperty("sslImplementationName", DynamicSslImplementation.class.getName());
System.setProperty("tobias.spring.ssl.keys-location", keysLocation.toUri().toString());
}
}
实现类非常简单。它只需提供自定义 SSLUtil 实例。
public class DynamicSslImplementation extends JSSEImplementation {
public DynamicSslImplementation() {
super();
}
@Override
public SSLUtil getSSLUtil(SSLHostConfigCertificate certificate) {
return new DynamicSslUtil(certificate);
}
}
SSLUtil 实例提供了我自己的 PathWatchingKeyManager,它将从某个目录返回 key 。
public class DynamicSslUtil extends JSSEUtil {
DynamicSslUtil(SSLHostConfigCertificate certificate) {
super(certificate);
}
@Override
public KeyManager[] getKeyManagers() {
return new KeyManager[]{new DynamicKeyManager()};
}
}
server.ssl.key-store
属性必须设置为NONE
。
这似乎有效。 Spring Boot 应用程序开始正常运行,并且 DynamicKeyManager 被要求提供 https 请求的证书。
如果这确实有效,我将在这里发布完整的解决方案。
关于spring-boot - 如何将 spring-boot-application 的静态 keystore 替换为动态 keystore ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58155994/