tomcatserver.xml

How to override Http11Nio2Protocol class to encrypt keystore password in server.xml


Followed the answer - Encrypt tomcat keystore password

Extend the Http11Nio2Protocol class like this:

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);
    }
}

I build a maven project and put the jar in 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>

When I use the origin password, it works normally. Then I changed certificateKeystorePassword, it failed, and the error info in catalina.out is:

** [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.

It seems like "keystore password was incorrect".

I searched 'setKeystorePass' in tomcat source code, but I can't find where it's called. So why can I extend the method to change the protocol configuration?

Thanks in advance!


Solution

  • The reason you can't find any call to setKeystorePass is that it is performed by reflection, mostly using IntrospectionUtils#setProperty (cf. Commons Digester for details).

    The configuration XML is used to create Java objects:

    So, if you want the bootstrap code to call your implementation of setKeystorePassword you need to define the connector in this way:

    <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)" />
    

    This syntax is obsolete since Tomcat 8.5 and was removed in Tomcat 10, but it is the easiest way to accomplish what you want.

    If you wish to use the new syntax, you'll need to do more modifications to the standard Http11NioProtocol:

    1. You'll need to create a proxy for SSLHostConfigCertificate that overrides just setCertificateKeystorePassword,
    2. You'll need to create a proxy for SSLHostConfigCertificate the overrides addCertificate which will call the original addCertificate with the proxy from point 1,
    3. You'll need to override addSSLHostConfig in your protocol by calling the original addSSLHostConfig with the proxy from the previous point.

    PS: you are not encrypting your password, you are encoding it. To decode an encoded password you just need to know the transformation (in your case Base64), to decrypt an encrypted password you need to know the encryption algorithm and a secret key. Then you'll probably need to encrypt the encryption key and so on... I find both procedures useless.