如何覆盖 Http11Nio2Protocol class 以加密 server.xml 中的密钥库密码
How to override Http11Nio2Protocol class to encrypt keystore password in server.xml
按照答案 - Encrypt tomcat keystore password
像这样扩展 Http11Nio2Protocol class:
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="^{}[]|"" >
<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.out里面的错误信息是:
** [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 (cf. Commons Digester 了解详细信息)。
配置XML用于创建Java个对象:
<Connector protocol="com.fine.security.ReSetHttpProtocol"
创建连接器和协议处理程序,
- 标签
<SSLHostConfig>
创建 SSLHostConfig
的实例,结束标签 </SSLHostConfig>
在连接器上调用 Connector#addSslHostConfig
,
- 这同样适用于
<Certificate>
,它创建 SSLHostConfigCertificate
的实例,配置它并在最后调用 SSLHostConfig#addCertificate
.
<Connector>
、<SSLHostConfig>
和 <Certificate>
标签的所有属性在相应对象上调用适当的 setters 或 setProperty
。例如。 port="8443"
调用了 setter setPort()
,但是 SSLEnabled="true"
调用了 setProperty("SSLEnabled", "true")
因为没有 setter,
因此,如果您希望 bootstrap 代码调用您的 setKeystorePassword
实现,您需要以这种方式定义连接器:
<Connector port="8443"
protocol="com.fine.security.ReSetHttpProtocol"
maxThreads="150"
scheme="https"
SSLEnabled="true"
relaxedQueryChars="^{}[]|""
sslEnabledProtocols="TLSv1.2"
ciphers="..."
keystoreFile="(absolute path of the .pfx file)"
keystoreType="JKS"
keystorePass="(encrypted password)" />
此语法自 Tomcat 8.5 起已过时,并已在 Tomcat 10 中删除,但它是完成所需内容的最简单方法。
如果您希望使用新语法,则需要对标准进行更多修改 Http11NioProtocol
:
- 您需要为
SSLHostConfigCertificate
创建一个仅覆盖 setCertificateKeystorePassword
、 的代理
- 您需要为
SSLHostConfigCertificate
覆盖 addCertificate
创建一个代理,它将使用第 1 点 的代理调用原始 addCertificate
- 您需要通过使用前一点的代理调用原始
addSSLHostConfig
来覆盖协议中的 addSSLHostConfig
。
PS:您没有加密您的密码,您正在编码 它。要解码编码密码,您只需要知道转换(在您的情况下为 Base64),要解密加密密码,您需要知道加密算法和密钥。然后你可能需要加密加密密钥等等......我发现这两个程序都没用。
好吧,尽管理解了 Piotr 的回答背后的想法,但我还是完全糊涂了。
他的所有评论大部分都是正确的(也许完全正确!),但是由于“Digester”正在驱动 SSLHostConfig 和 SSLHostConfigCertificate 的实例化和添加,这里的代理模式不能很好地工作。
入口点是在连接器上配置的 class,但是您无法包装(或者我找不到如何包装)来自自定义连接器 class 的 SSLHostConfig。如果您扩展 SSLHostConfig 并覆盖连接器中的 class 以调用该“代理”,您最终会创建一个新的代理实例,然后您必须将属性(使用反射或手动)设置为“克隆”或保留消化器解析的最重要的属性。请记住,无论覆盖方法如何,摘要都会创建每个 XML 标记的实例。
在真正考虑到我没有很好地理解之后,当我意识到我已经考虑了几个小时应该工作但没有的东西时,我疯了(因为我不小心重复了server.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 中测试过,它确实有效。
欢迎提出意见、建议和更正。
按照答案 - Encrypt tomcat keystore password
像这样扩展 Http11Nio2Protocol class:
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="^{}[]|"" >
<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.out里面的错误信息是:
** [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 (cf. Commons Digester 了解详细信息)。
配置XML用于创建Java个对象:
<Connector protocol="com.fine.security.ReSetHttpProtocol"
创建连接器和协议处理程序,- 标签
<SSLHostConfig>
创建SSLHostConfig
的实例,结束标签</SSLHostConfig>
在连接器上调用Connector#addSslHostConfig
, - 这同样适用于
<Certificate>
,它创建SSLHostConfigCertificate
的实例,配置它并在最后调用SSLHostConfig#addCertificate
. <Connector>
、<SSLHostConfig>
和<Certificate>
标签的所有属性在相应对象上调用适当的 setters 或setProperty
。例如。port="8443"
调用了 settersetPort()
,但是SSLEnabled="true"
调用了setProperty("SSLEnabled", "true")
因为没有 setter,
因此,如果您希望 bootstrap 代码调用您的 setKeystorePassword
实现,您需要以这种方式定义连接器:
<Connector port="8443"
protocol="com.fine.security.ReSetHttpProtocol"
maxThreads="150"
scheme="https"
SSLEnabled="true"
relaxedQueryChars="^{}[]|""
sslEnabledProtocols="TLSv1.2"
ciphers="..."
keystoreFile="(absolute path of the .pfx file)"
keystoreType="JKS"
keystorePass="(encrypted password)" />
此语法自 Tomcat 8.5 起已过时,并已在 Tomcat 10 中删除,但它是完成所需内容的最简单方法。
如果您希望使用新语法,则需要对标准进行更多修改 Http11NioProtocol
:
- 您需要为
SSLHostConfigCertificate
创建一个仅覆盖setCertificateKeystorePassword
、 的代理
- 您需要为
SSLHostConfigCertificate
覆盖addCertificate
创建一个代理,它将使用第 1 点 的代理调用原始 - 您需要通过使用前一点的代理调用原始
addSSLHostConfig
来覆盖协议中的addSSLHostConfig
。
addCertificate
PS:您没有加密您的密码,您正在编码 它。要解码编码密码,您只需要知道转换(在您的情况下为 Base64),要解密加密密码,您需要知道加密算法和密钥。然后你可能需要加密加密密钥等等......我发现这两个程序都没用。
好吧,尽管理解了 Piotr 的回答背后的想法,但我还是完全糊涂了。 他的所有评论大部分都是正确的(也许完全正确!),但是由于“Digester”正在驱动 SSLHostConfig 和 SSLHostConfigCertificate 的实例化和添加,这里的代理模式不能很好地工作。 入口点是在连接器上配置的 class,但是您无法包装(或者我找不到如何包装)来自自定义连接器 class 的 SSLHostConfig。如果您扩展 SSLHostConfig 并覆盖连接器中的 class 以调用该“代理”,您最终会创建一个新的代理实例,然后您必须将属性(使用反射或手动)设置为“克隆”或保留消化器解析的最重要的属性。请记住,无论覆盖方法如何,摘要都会创建每个 XML 标记的实例。
在真正考虑到我没有很好地理解之后,当我意识到我已经考虑了几个小时应该工作但没有的东西时,我疯了(因为我不小心重复了server.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 中测试过,它确实有效。
欢迎提出意见、建议和更正。