如何重写Http11Nio2Protocol类来加密server.xml中的keystore密码 &



跟随答案-加密tomcat密钥库密码

像这样扩展Http11Nio2Protocol类:

public class ReSetHttpProtocol extends Http11Nio2Protocol {
@Override
public void setKeystorePass(String certificateKeystorePassword) {
Decoder decoder = Base64.getDecoder();
String password = new String(decoder.decode(certificateKeystorePassword));
super.setKeystorePass(password);
}
}

我构建了一个maven项目,并把jar放到tomcat/lib中。

/conf/server.xml:

<Connector port="8443" protocol="com.fine.security.ReSetHttpProtocol"
maxThreads="150" scheme="https" SSLEnabled="true" relaxedQueryChars="^{}[]|&quot;" >
<SSLHostConfig   sslEnabledProtocols="TLSv1.2" ciphers="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" >
<Certificate certificateKeystoreFile="(absolute path of the .pfx file)"
certificateKeystoreType="JKS" certificateKeystorePassword="(encrypted password)" />
</SSLHostConfig>
</Connector>

当我使用原始密码时,它可以正常工作。然后我更改了certificateKeystorePassword,它失败了,并且在catalina中的错误信息。是:

** [main] org.apache.catalina.core.StandardService.initInternal Failed to initialize connector [Connector[com.fine.security.ReSetHttpProtocol-8443]]
org.apache.catalina.LifecycleException: Protocol handler initialization failed
at org.apache.catalina.connector.Connector.initInternal(Connector.java:1032)
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136)
at org.apache.catalina.core.StandardService.initInternal(StandardService.java:552)
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136)
at org.apache.catalina.core.StandardServer.initInternal(StandardServer.java:848)
at org.apache.catalina.startup.Catalina.load(Catalina.java:662)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:472)
Caused by: java.lang.IllegalArgumentException: keystore password was incorrect
at org.apache.catalina.startup.Catalina.load(Catalina.java:639)
at org.apache.catalina.startup.Catalina.load(Catalina.java:662)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:472)
Caused by: java.lang.IllegalArgumentException: keystore password was incorrect
at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:100)
at org.apache.tomcat.util.net.AbstractJsseEndpoint.initialiseSsl(AbstractJsseEndpoint.java:72)
at org.apache.tomcat.util.net.Nio2Endpoint.bind(Nio2Endpoint.java:158)
at org.apache.tomcat.util.net.AbstractEndpoint.init(AbstractEndpoint.java:1118)
at org.apache.tomcat.util.net.AbstractJsseEndpoint.init(AbstractJsseEndpoint.java:223)
at org.apache.coyote.AbstractProtocol.init(AbstractProtocol.java:587)
at org.apache.coyote.http11.AbstractHttp11Protocol.init(AbstractHttp11Protocol.java:74)
at org.apache.catalina.connector.Connector.initInternal(Connector.java:1030)
... 13 more
Caused by: java.io.IOException: keystore password was incorrect
at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:2059)
at sun.security.provider.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:238)
at sun.security.provider.JavaKeyStore$DualFormatJKS.engineLoad(JavaKeyStore.java:70)
at java.security.KeyStore.load(KeyStore.java:1445)
at org.apache.tomcat.util.security.KeyStoreUtil.load(KeyStoreUtil.java:69)
at org.apache.tomcat.util.net.SSLUtilBase.getStore(SSLUtilBase.java:216)
at org.apache.tomcat.util.net.SSLHostConfigCertificate.getCertificateKeystore(SSLHostConfigCertificate.java:206)
at org.apache.tomcat.util.net.SSLUtilBase.getKeyManagers(SSLUtilBase.java:282)
at org.apache.tomcat.util.net.SSLUtilBase.createSSLContext(SSLUtilBase.java:246)
at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:98)
... 20 more
Caused by: java.security.UnrecoverableKeyException: failed to decrypt safe contents entry: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

似乎"密钥库密码不正确"。

我在tomcat源代码中搜索了'setKeystorePass',但我找不到它被调用的地方。那么,为什么我可以扩展该方法来更改协议配置呢?

提前感谢!

你找不到任何对setKeystorePass的调用的原因是它是通过反射执行的,主要使用IntrospectionUtils#setProperty(参见Commons Digester了解详细信息)。

配置XML用于创建Java对象:

  • <Connector protocol="com.fine.security.ReSetHttpProtocol"创建连接器和协议处理程序,
  • 标签<SSLHostConfig>创建SSLHostConfig的实例,关闭标签</SSLHostConfig>调用连接器上的Connector#addSslHostConfig
  • 同样适用于<Certificate>,它创建SSLHostConfigCertificate的实例,配置它,最后调用SSLHostConfig#addCertificate
  • <Connector><SSLHostConfig><Certificate>标签的所有属性调用相应对象上相应的setter或setProperty。例如,port="8443"调用settersetPort(),但SSLEnabled="true"调用setProperty("SSLEnabled", "true"),因为没有setter,

因此,如果您希望引导代码调用setKeystorePassword的实现,则需要以这种方式定义连接器:

<Connector port="8443"
protocol="com.fine.security.ReSetHttpProtocol"
maxThreads="150"
scheme="https"
SSLEnabled="true"
relaxedQueryChars="^{}[]|&quot;"
sslEnabledProtocols="TLSv1.2"
ciphers="..."
keystoreFile="(absolute path of the .pfx file)"
keystoreType="JKS"
keystorePass="(encrypted password)" />

这种语法从Tomcat 8.5开始就过时了,在Tomcat 10中被删除了,但它是实现您想要的功能的最简单方法。

如果您希望使用新的语法,您需要对标准的Http11NioProtocol:

做更多的修改。
  1. 你需要为SSLHostConfigCertificate创建一个代理,覆盖setCertificateKeystorePassword
  2. 你需要为SSLHostConfigCertificate创建一个代理,覆盖addCertificate,它将从点1的代理调用原始addCertificate
  3. 你需要在你的协议中通过调用原始的addSSLHostConfig来覆盖addSSLHostConfig

p:您没有加密您的密码,您是编码它。要解码编码的密码,您只需要知道转换(在您的示例中是Base64),要解密加密的密码,您需要知道加密算法和秘钥。然后你可能需要加密加密密钥等等……我觉得这两种方法都没用。

好吧,尽管我已经理解了Piotr回答背后的想法,但我最终还是完全困惑了。他所有的评论基本上是对的(也许是完全正确的!)驱动SSLHostConfig和SSLHostConfigCertificate的实例化和添加,这里的代理模式工作得不是很好。入口点是在连接器上配置的类,但是您无法包装(或者我找不到如何包装)自定义连接器类中的SSLHostConfig。如果您扩展SSLHostConfig并覆盖连接器中的类以调用该"代理",则最终将创建代理的新实例,然后必须设置属性(使用反射或手动)以某种"克隆"。或者保留消化器解析的最重要的属性。请记住,消化器创建每个XML标记的实例,而不必覆盖方法。

在认真考虑我没有理解好之后,当我意识到我已经花了几个小时思考应该工作的东西,但没有(因为我不小心复制了"证书")时,我疯了。

最后我想到了:

import java.util.Optional;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
public class CustomHttp11NioProtocol extends org.apache.coyote.http11.Http11NioProtocol {
@Override
public void addSslHostConfig(SSLHostConfig conf) {
Optional<SSLHostConfigCertificate> cert = conf.getCertificates().stream().findFirst();
if (cert.isPresent())
cert.get().setCertificateKeystorePassword(
new TomcatPasswordDecryption().decrypt(cert.get().getCertificateKeystorePassword()));
super.addSslHostConfig(conf);
}
}
In my case I only have one certificate, so findFirst() is enough. But you could do the password replacement for the entire stream.
Actually this is even better:
@Override
public void addSslHostConfig(SSLHostConfig conf) {
conf.getCertificates().stream().forEach(a -> a.setCertificateKeystorePassword(
new TomcatPasswordDecryption().decrypt(a.getCertificateKeystorePassword())));

super.addSslHostConfig(conf);
}

我已经在Tomcat 9和JDK11中测试过了,它确实有效。

欢迎评论、建议和更正。

最新更新