如何用动态密钥库替换静态密钥库以用于spring-boot-application

时间:2019-09-29 14:07:06

标签: 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似乎仅是为了为其提供服务提供者接口所必需的。

@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中,该InputStream用于加载内部使用的密钥库。

在以下代码片段中,您可以看到我如何尝试编写已经存在的密钥库的内容。现在没有动态。我只想看看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();
    }
  }
}

我可以看到密钥库已加载,但是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作为Servlt容器为Spring Boot应用程序实现并使用动态密钥库/ trustore吗?

欢迎任何帮助。 托比亚斯

1 个答案:

答案 0 :(得分:0)

好吧,我不知道这是否是最终的解决方案,但现在看来比我上面介绍的第一种方法更有希望(并且不太复杂)。

同样,这一切都始于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,它将从某个目录返回密钥。

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请求的证书请求。

如果这确实可行,我将在此处发布完整的解决方案。

相关问题