How to use the certificate for Tomcat

…And for chain.pem:
cat www.mydomain.cert.pem intermediate.cert.pem > chain.pem
?

We’re talking about Let’s Encrypt here, right? Because the official client already provides you with fullchain.pem.

If not, then you’re a) not giving us enough information to work with and b) are you at the right place to ask these questions? :stuck_out_tongue: This is the Let’s Encrypt Community :wink: Questions about random TLS/certificate questions which do not have anything to do with Let’s Encrypt are probably best asked somewhere else as far as I’m concerned. You’d probably have more luck, because I’m not really understanding your problem to be honest… :slight_smile:

Hi thanks much @melo for this solution. LetsEncrypt works great now in my environment for Chrome browsers. The problem is that it doesn’t work for Firefox. I’m looking at @eswn’s solution for the FF problem in Apache here: Cert not work in firefox. But importing the chain file he refers to to my java keystore does not resolve the firefox problem. How do I get LetsEncrypt working correctly for tomcat and Firefox?

I’m not really familiar with tomcat, but generally when you run into issues like this where things work in one browser (or on one device) but not in another, it’s often because the correct intermediate certificate is missing. Browsers cache those intermediate certificates, so if (for example) you visited some other site using a recent Let’s Encrypt certificate in Chrome but not in Firefox, that would make Chrome work but Firefox fail. Basically, don’t think about it as a browser issue, but rather something that’s missing from your server configuration. I would recommend using SSL Labs to verify you’re serving the correct intermediate certificate.

If you find out you’re serving the wrong intermediate certificate and need to get the right one, you can find both the old one and the current one (X3) here. You’ll want to pass that to openssl as -CAfile chain.pem when creating your PKCS12 file.

Hi @vlott I am glad my post is helping. However I am afraid you might have got it a little wrong. @pfg summarized it perfectly.

There is no issue with the browser itself. We must ensure you are serving the entire chain required to accept your certificate as trusted. SSL Labs is a good tool to check whether you serve the entire certificate chain or if something is missing.
In case you serve on a port other than 443, you can use SSL Checker and provide your domain and custom port.
Run the test on your site. It will list the server certificate and any additional certificates, if provided. You must also provide the intermediate certificate for the certification path to be complete

To achieve this, as stated in my post, it is important that you use the fullchain.pem file that was generated together with your certificate like so:
openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out fullchain_and_key.p12 -name tomcat

I later use keytool to convert this to JKS and setup Tomcat’s connector accordingly, but I was told that Tomcat supports PKCS12 keystores, so the keytool step might be unnecessary.

Hope this helps.

1 Like

I had a problem where when I convert the cert generated into JKS the DST Root X3 cert in the chain is missing.

For some weird reason, ended up having to create the cert chain again this way...

openssl pkcs12 -in certificate-all.pfx -out clientcert.pem -nodes -clcerts
cat clientcert.pem lets-encrypt-x3-cross-signed.pem dst-root-ca-x3.pem >> clientcertchain.pem
openssl pkcs12 -export -in clientcertchain.pem -out clientcertchain.pfx
keytool -importkeystore -srckeystore clientcertchain.pfx -srcstoretype pkcs12 -destkeystore clientcertchain.jks -deststoretype JKS

Hi @sintroo I believe that it is perfectly fine to NOT provide the DST Root X3 certificate. This is a certificate from IdenTrust, a widely accepted and trusted certificate authority. This means that their certificate is included in several certificate authority trust stores, e.g. the ones of operating systems (used by browsers) or the Java CA trust store.

For a valid certification path it is fine to NOT provide the root CA certificate.
Your generated cert says it is signed by LE, so you provide the LE cert for it to be available for validation. The LE cert is signed by DST, so for validation DST will be looked up in the available root CA trust store, were it will be found. This will give you a valid path of your-cert -> LE -> DST and you only need to provide your cert and every other cert required to build a path to a root cert, which in our case this is just the one LE cert.

Hope this helps.

Thanks for the info @melo. Then could it be because the DST Root X3 certificate is not included in the default java cacerts?

Others having the same problem discussed here: https://github.com/ebekker/ACMESharp/issues/119

@sintroo I guess it depends on the used java version. Java comes with keytool. You can use it to list the entries of a keystore. To list the trusted CA certs you could try running this on your server:
keytool -list -v -keystore $JAVA_HOME/jre/lib/security/cacerts

But I am not sure what your problem is exactly.
The DST Root X3 cert missing from the java trusted CA store would only be relevant when accessing your server with a java client and trying to validate the provided certificate.
There is nothing you need to to on your server for the DST Root X3 cert.

Hello, melo.

Thank you for your solution.
Also I’d like to tell that it can be used pkcs12 keystore without jks converting.

Server.xml should look like this.

`< Connector
port="443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="200"
SSLEnabled="true"
scheme="https"
secure="true"
clientAuth="false"
sslProtocol=“TLS"
keystoreFile=”[path to p12-file]“
keystoreType=“PKCS12"
keystorePass=”[selected pkcs12 keystore password]” />

`

Hi @vaysman, thank you for your feedback!

This confirms my suspicion that the convert to JKS step is unnecessary.
One step less means slightly reduced chance to make mistakes =)
Thank you for sharing the connector configuration.

For me this means that in future I can just use the PKCS12 file and only need to change the keystoreFile value accordingly and add the keystoreType attribute with value PKCS12 to my connector.

1 Like

Thanks @melo, I saw your updated command further down in the other thread. This worked great for me and I am up and running in SpringBoot. If anyone is interested in what I did to get working with the embedded tomcat in SpringBoot, just reply and I’ll post.

1 Like

Thanks @melo, I struggled for so many hours and thanks to your guide I now have a working tomcat server on a raspberry pi with free ssl certificates from let’s encrypt and a free subdomain from selfhost.eu :slight_smile:

1 Like

I have a new preferred solution for HTTPS to Tomcat with Let’s Encrypt certificate. I do not configure Tomcat for HTTPS in the first place!
In case your setup consists of both a web server and Tomcat, consider setting up a reverse proxy on your web server and let it handle HTTPS for Tomcat. This way you can benefit from Let’s Encrypts automatic certifcate renewal capabilities that update the certificates your web server uses and you are still able to securely access Tomcat through HTTPS without manual keystore creation or anything like that.
I use a Synology DiskStation, which added LE support and a reverse proxy feature and I now have a fully automatic, one time setup solution I really like =)

In case anyone is interested in this specific setup, I described it in a blog post:

Hi @vlott, I am running a SpringBoot application with embedded Tomcat and am trying to get it to work with Let’s Encrypt, and searching through the forum came across your comment here. Did you post the steps you used? It would be super super helpful to us. I’ve installed certbot, and generated fullchain.pem & the PKCS12 file, as in some comments above, but am getting errors on the conversion to JKS (keytool gives me a BadPaddingException).

Did you take the route of just using the PKCS12 file with Spring-Boot/Tomcat and if so how did it go? Or did you use the reverse proxy route?

Here are the steps I used to generate certificates and get them working for all browsers in Spring Boot:

  • Make sure firewall will allow the letsencrypt client to receive a callback on port 80 of the server at the domain(s) being configured in the certificate.
  • Run the letsencrypt client:
    sudo /home/ec2-user/letsencrypt/letsencrypt-auto certonly --standalone --standalone-supported-challenges http-01
  • When prompted enter the domains that the letsencrypt server will call back to verify as per above
  • You should have a successful run and you should see the certificates in the /etc/letsencrypt/live directory
  • Create PKCS12 keystore (to be imported to jks later):
    sudo openssl pkcs12 -export -in /etc/letsencrypt/archive/yourDomain.com/fullchainx.pem -inkey /etc/letsencrypt/archive/yourDomain.com/privkeyx.pem -out yourDomain-cert_and_key.p12 -name tomcat (where fullchainx is fullchain1.pem, fullchain2.pem, etc., from the new certificate. This will be incremented every time new certs are generated. IMPORTANT! Use the fullchainx.pem instead of the certx or chainx or it won't work in Firefox)
  • Convert PKCS12 Keystore to Java Keystore:
    /usr/java/jre1.7.0_65/bin/keytool -importkeystore -deststorepass yourKeyPassword -destkeypass yourPassword -destkeystore yourDomain-keystore.jks -srckeystore yourDomain-cert_and_key.p12 -srcstoretype PKCS12 -srcstorepass yourPassword -alias tomcat
  • Check Java Keystore:
    keytool -list -keystore yourDomain-keystore.jks
  • Copy keystore file to springBootJarLocation/ssl/yourDomain-keystore.jks on prod server

Spring Boot configuration

  • Add entries to prod environment and configure SpringBoot to read keystore off disk (application-prod.properties) (and autowire into Configuration-annotated class)

custom.server.port=8443
custom.server.ssl.key-store=file:/opt//ssl/yourDomain-keystore.jks
custom.server.ssl.key-store-password=yourPassword
custom.server.ssl.key-password=yourPassword

  • Add entries as needed to local environment for testing also

  • Add a bean for a custom EmbeddedServletContainerFactory that configures all requests to be encrypted and redirected to SSL. Also, this bean will add a second connector for port 443/SSL:

    @Bean

  public EmbeddedServletContainerFactory servletContainer() {
      TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {
          \@Override
          protected void postProcessContext(Context context) {
              SecurityConstraint securityConstraint = new SecurityConstraint();
              securityConstraint.setUserConstraint("CONFIDENTIAL");
              SecurityCollection collection = new SecurityCollection();
              collection.addPattern("/*");
              securityConstraint.addCollection(collection);
              context.addConstraint(securityConstraint);
          }

      };
      tomcat.addAdditionalTomcatConnectors(createSslConnector());
      return tomcat;
  }

  private Connector createSslConnector() {
      Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
      Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
      try {
          File keystore = new UrlResource(serverSslKeystore).getFile();
          File truststore = new UrlResource(serverSslKeystore).getFile();
          connector.setScheme("https");
          connector.setSecure(true);
          connector.setPort(serverPort);
          protocol.setSSLEnabled(true);
          protocol.setKeystoreFile(keystore.getAbsolutePath());
          protocol.setKeystorePass(serverSslKeystorePassword);
          protocol.setKeyPass(serverSslKeyPassword);
          protocol.setTruststoreFile(truststore.getAbsolutePath());
          protocol.setTruststorePass(serverSslKeystorePassword);
          protocol.setKeyAlias("tomcat");
          return connector;
      } catch (IOException ex) {
          throw new IllegalStateException("can't access keystore: [" + "keystore"
                  + "] or truststore: [" + "keystore" + "]", ex);
      }
  }

@vlott: Thank you! Just followed those and have the cert installed and all working very nicely. Thanks a lot for the quick reply & clear instructions.

Tomcat 8.5.3 now has native support for openssl

Assuming you are using OpenSSL, & Tomcat 8.5.3 you can use the LetsEncrypt certificat *.pem files directly like this:

example connector in server.xml:

2 Likes

I get an exception with tomcat 9.0.0.M8:

29-Jul-2016 09:58:52.366 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
29-Jul-2016 09:58:52.384 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
29-Jul-2016 09:58:52.387 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["https-jsse-nio-8443"]
29-Jul-2016 09:58:52.742 SEVERE [main] org.apache.coyote.AbstractProtocol.init Failed to initialize end point associated with ProtocolHandler ["https-jsse-nio-8443"]
 java.security.KeyStoreException: Cannot store non-PrivateKeys
    at sun.security.provider.JavaKeyStore.engineSetKeyEntry(JavaKeyStore.java:258)
    at sun.security.provider.JavaKeyStore$JKS.engineSetKeyEntry(JavaKeyStore.java:56)
    at sun.security.provider.KeyStoreDelegator.engineSetKeyEntry(KeyStoreDelegator.java:117)
    at sun.security.provider.JavaKeyStore$DualFormatJKS.engineSetKeyEntry(JavaKeyStore.java:70)
    at java.security.KeyStore.setKeyEntry(KeyStore.java:1140)
    at org.apache.tomcat.util.net.jsse.JSSEUtil.getKeyManagers(JSSEUtil.java:302)
    at org.apache.tomcat.util.net.AbstractJsseEndpoint.initialiseSsl(AbstractJsseEndpoint.java:90)
    at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:245)
    at org.apache.tomcat.util.net.AbstractEndpoint.init(AbstractEndpoint.java:839)
    at org.apache.coyote.AbstractProtocol.init(AbstractProtocol.java:558)
    at org.apache.coyote.http11.AbstractHttp11Protocol.init(AbstractHttp11Protocol.java:65)
    at org.apache.catalina.connector.Connector.initInternal(Connector.java:1010)
    at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:107)
    at org.apache.catalina.core.StandardService.initInternal(StandardService.java:549)
    at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:107)
    at org.apache.catalina.core.StandardServer.initInternal(StandardServer.java:873)
    at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:107)
    at org.apache.catalina.startup.Catalina.load(Catalina.java:606)
    at org.apache.catalina.startup.Catalina.load(Catalina.java:629)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:311)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:494)

29-Jul-2016 09:58:52.744 SEVERE [main] org.apache.catalina.core.StandardService.initInternal Failed to initialize connector [Connector[HTTP/1.1-8443]]
 org.apache.catalina.LifecycleException: Failed to initialize component [Connector[HTTP/1.1-8443]]
    at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:112)
    at org.apache.catalina.core.StandardService.initInternal(StandardService.java:549)
    at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:107)
    at org.apache.catalina.core.StandardServer.initInternal(StandardServer.java:873)
    at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:107)
    at org.apache.catalina.startup.Catalina.load(Catalina.java:606)
    at org.apache.catalina.startup.Catalina.load(Catalina.java:629)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:311)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:494)
Caused by: org.apache.catalina.LifecycleException: Protocol handler initialization failed
    at org.apache.catalina.connector.Connector.initInternal(Connector.java:1013)
    at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:107)
    ... 12 more
Caused by: java.security.KeyStoreException: Cannot store non-PrivateKeys
    at sun.security.provider.JavaKeyStore.engineSetKeyEntry(JavaKeyStore.java:258)
    at sun.security.provider.JavaKeyStore$JKS.engineSetKeyEntry(JavaKeyStore.java:56)
    at sun.security.provider.KeyStoreDelegator.engineSetKeyEntry(KeyStoreDelegator.java:117)
    at sun.security.provider.JavaKeyStore$DualFormatJKS.engineSetKeyEntry(JavaKeyStore.java:70)
    at java.security.KeyStore.setKeyEntry(KeyStore.java:1140)
    at org.apache.tomcat.util.net.jsse.JSSEUtil.getKeyManagers(JSSEUtil.java:302)
    at org.apache.tomcat.util.net.AbstractJsseEndpoint.initialiseSsl(AbstractJsseEndpoint.java:90)
    at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:245)
    at org.apache.tomcat.util.net.AbstractEndpoint.init(AbstractEndpoint.java:839)
    at org.apache.coyote.AbstractProtocol.init(AbstractProtocol.java:558)
    at org.apache.coyote.http11.AbstractHttp11Protocol.init(AbstractHttp11Protocol.java:65)
    at org.apache.catalina.connector.Connector.initInternal(Connector.java:1010)
    ... 13 more

Relevant part from server.xml:

  <Service name="Catalina">

    <!-- Connector port="8080" protocol="HTTP/1.1"... -->
    <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
               connectionTimeout="20000" redirectPort="8443" />
    
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               defaultSSLHostConfigName="abcde.xyz-informatik.de">
        <SSLHostConfig hostName="abcde.xyz-informatik.de">
            <!-- Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                         type="RSA" /-->
            <Certificate certificateFile="conf/ssl/abcde.xyz-informatik.de/domain.crt"
                         certificateChainFile="conf/ssl/abcde.xyz-informatik.de/chain.crt"
                         certificateKeyFile="conf/ssl/abcde.xyz-informatik.de/domain.key"
                         certificateKeyPassword=""
                         certificateKeyAlias=""
                         type="RSA" />
        </SSLHostConfig>
    </Connector>

… Maybe reproducing this can be done with uninstalling openssl… Just a guess…
I just tried with tomcat 9.0.0.M9 and it gives completely different exceptions, but does not work neither.
I want to rely purely on java, to not be affected by openssl security holes. So I created a virtual server with no openssl installed in it.

@Coleman-rik - Thanks for the post!

I just wanted to give everyone a heads up that the method @Coleman-rik documented is broken in 8.5.4, so if you are testing this out either use 8.5.3 or wait until the 8.5.5 is released with this fixed. Hopefully this will save some frustration as I just spent a few hours with 8.5.4!