Я хочу динамически добавлять/заменять SSL-сертификаты в моем приложении весенней загрузки (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
кажется необходимым только для того, чтобы предоставить ему интерфейс поставщика услуг.
@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();
}
}
}
Теперь при запуске будет загружено хранилище ключей. И поскольку я предоставляю SslStoreProvider, мое хранилище ключей будет загружено SslStoreProviderUrlStreamHandlerFactory путем запроса моего PathWatchingKeyStoreSpi на сохранение хранилища ключей в ByteArrayOutputStream, содержимое которого, наконец, копируется в InputStream, который используется для загрузки внутреннего хранилища ключей.
В следующем фрагменте кода вы можете увидеть, как я пытаюсь записать содержимое уже существующего хранилища ключей. Сейчас вообще никакой динамики. Я только хочу посмотреть, запускается ли приложение весенней загрузки со всеми этими пользовательскими классами. Но это не так.
@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();
}
}
}
Я вижу, что хранилище ключей загружено, но как только SSLUtilBase пытается прочитать ключ из этого хранилища, возникает исключение 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
Я создал статическое хранилище ключей, которое я использую здесь, следующим образом:
keytool -genkey -alias tomcat -keyalg RSA
Во-первых, перспективно ли то направление, в котором я собираюсь решать свою проблему? Или я совсем не прав? Сначала я пытался вводить только свои собственные X509ExtendedKeyManager
. В отладчике я мог видеть, что сертификат для входящего запроса запрашивается у диспетчера ключей, но, тем не менее, конечная точка tomcat, похоже, инициализируется хранилищем ключей без участия диспетчера.
Кто-нибудь когда-либо пытался внедрить и использовать динамическое хранилище ключей/доверенное хранилище для весеннего загрузочного приложения, используя tomcat в качестве контейнера для серверов?
Любая помощь приветствуется. Тобиас