spring-boot - 如何将 spring-boot-application 的静态 keystore 替换为动态 keystore

标签 spring-boot ssl tomcat keystore tomcat9

我想动态添加/替换 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/

相关文章:

java - 与基于 Spring Security 的身份验证与 mongodb 后端数据库混淆

cookies - 通过 HTTPS 感知安全 cookie

java - HttpClient 支持多种 TLS 协议(protocol)

ssl - 是否可以在带有 NginX 的 Odoo 中使用 SSL 以避免标准端口(80 和 443)?

java - Spring Boot Actuators 中是否有检查子服务健康状况的标准方法?

java - Spring Boot如何将ArrayList转换为Pageable对象

java - 如何强制 CLOSE_WAIT 停止

java - 使用证书 .cer 在 Tomcat 上安装 SSL

当 id 为 String 时,Java mongoTemplate findOne 查询不返回结果

tomcat - 限制 Tomcat 中的文件访问