【问题标题】:How to replace the static keystore with a dynamic one for a spring-boot-application如何将静态密钥库替换为 spring-boot-application 的动态密钥库
【发布时间】:2020-01-29 01:06:17
【问题描述】:

我想在我的 spring boot (tomcat) 应用程序中动态添加/替换 SSL 证书,而无需重新启动它。我还有很长的路要走,但目前我遇到了 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 中。

在下面的代码 sn-p 中,您可以看到我如何尝试编写已经存在的密钥库的内容。现在完全没有动态。我只想看看 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 作为servelt 容器的 Spring Boot 应用程序实现和使用动态密钥库/信任库?

欢迎任何帮助。 托比亚斯

【问题讨论】:

    标签: spring-boot ssl tomcat keystore tomcat9


    【解决方案1】:

    好的,我不知道这是否是最终解决方案,但现在它似乎比我上面描述的第一种方法更有希望(并且不太复杂)。

    这一切还是从 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 请求提供证书。

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

    【讨论】:

    • 有效吗?因为我正在尝试配置 SslStoreProvider 并且遇到了与您相同的“填充”异常。
    • 我不知道。我很抱歉。我们通过在作为 TLS 端点的 Java 应用程序前面使用代理来解决问题。
    猜你喜欢
    • 1970-01-01
    • 2017-01-27
    • 1970-01-01
    • 2020-11-18
    • 2017-07-23
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多