3- Spring Boot with self signed certificate
4- Spring Boot with Let’s encrypt certificate
Introduction
This article describes how to use Spring Boot with a self signed certificate.
Then with we will deploy the Spring Boot application on AWS and issue a Let’s encrypt certificate against a dynamic DNS service Duck DNS.
Versions used
- Java 11 (use a version greater than 11.0.2 because of this bug), I downloaded the OpenJDK from https://adoptium.net/
- Spring Boot 2.7.7
Spring Boot with self signed certificate
The full project can be found here, I’m explaining below the configuration part.
I used the PKCS12 (Public Key Cryptographic Standards) format for the certificate, another supported format is the JKS (Java KeyStore), PKCS12 is more popular.
Keytool has been used for the certificate generation, this is provided by your JDK, the command line I used is below:
keytool -genkeypair -alias springbootselfsigned -keyalg RSA -keysize 2048 -keystore springbootselfsigned.p12 -storetype PKCS12 -validity 3650
This generates a file named springbootselfsigned.p12 according to my keystore argument in the command line above.
I copied this file in the project classpath inside the src/main/resources folder.
See the Spring Boot application.yml configuration below, I’m reaching the generated file through the server.ssl.key-store attribute, note also that you need to reference the password that you entered during the certificate creation:
# Spring Boot server port
server:
port: 8443
ssl:
key-store-type: PKCS12 # The format used for the keystore. It could be set to JKS in case it is a JKS file
key-store: classpath:springbootselfsigned.p12 # The path to the keystore containing the certificate
key-store-password: password1234 # The password used to generate the certificate
key-alias: springbootselfsigned # The alias mapped to the certificate
security:
require-ssl: true
Only HTTPS traffic is allowed here since we set server.security.require-ssl to true.
Let’s start the application and got to https://localhost:8443/test where a REST endpoint has been defined in my project.
Some browsers will ask you if you really want to carry on since the certificate is self signed as you can see below:
At this stage we manged to run Spring Boot with a self signed certificate, let’s setup a legit certificate now.
Spring Boot with Let’s encrypt certificate
Prerequisites:
- Provision an EC2 instance on AWS, I choose a t2.micro with the latest ubuntu.
- Create an account on Duck DNS and set a domain there, as you can see my domain is nbodev.duckdns.org:
Deploy your Spring Boot project on the EC2 instance:
. Download the JDK and transfer it to your instance with scp or any other tool:
- Set your java environment, unzip the JDK archive and set your path correctly, in my case I added this in the .bashrc file:
...
export JAVA_HOME=/home/ubuntu/jdk-11.0.17+8
export PATH=$JAVA_HOME/bin:$PATH
- Test this by running the following: (in case you are in the same session in which you updated the .bashrc file, run source .bashrc):
ubuntu@ip-172-31-1-34:~$ java -version
openjdk version "11.0.17" 2022-10-18
OpenJDK Runtime Environment Temurin-11.0.17+8 (build 11.0.17+8)
OpenJDK 64-Bit Server VM Temurin-11.0.17+8 (build 11.0.17+8, mixed mode)
- Compile the application we defined earlier by running mvn clean install and transfer the jar file from the target directory to your EC2:
- Run the following:
java -jar springboot-https-0.0.1-SNAPSHOT.jar
- Make sure you open the port 8443 as this is the one we configured for our app and get the Public IPv4 address of your EC2 from the EC2 dashboard:
- Go to the https://<Public IPv4 address>:8443/test , as you see below we still use the self signed certificate that we generated previously, that’s why we have such warning:
Generate a new certificate for our domain:
- Go to ttps://www.duckdns.org/ and update the IP address for your domain, it should target the Public IPv4 address of your EC2:
- Open the port 80 on your EC2, else the generation of the certificate will fail:
- Install certbot on the EC2 instance, below the ubuntu commands:
sudo apt-get update
sudo apt-get install certbot
- Generate the certificate for our subdomain nbodev.duckdns.org:
sudo certbot certonly --standalone -d nbodev.duckdns.org
- At this stage we need to produce a PKCS12 certificate as we did with the self signed certificate, in order to produce such file we run:
sudo openssl pkcs12 -export -in /etc/letsencrypt/live/nbodev.duckdns.org/fullchain.pem -inkey /etc/letsencrypt/live/nbodev.duckdns.org/privkey.pem -out springbootletsencrypt.p12 -name springbootletsencrypt -CAfile /etc/letsencrypt/live/nbodev.duckdns.org/chain.pem -caname root
- Make sure you change the owner of this file as it belongs to the root user:
sudo chown ubuntu:ubuntu springbootletsencrypt.p12
- Now let’s alter the application.yml configuration and restart our application, we will transfer the application.yml file that we already have and we are going to use it as an external file when we start the application, the application.yml updated section looks like, note that we used the exact same password as before for this certificate:
# Spring Boot server port
server:
port: 8443
ssl:
key-store-type: PKCS12 # The format used for the keystore. It could be set to JKS in case it is a JKS file
key-store: /home/ubuntu/springbootletsencrypt.p12 # The path to the keystore containing the certificate
key-store-password: password1234 # The password used to generate the certificate
key-alias: springbootletsencrypt # The alias mapped to the certificate
security:
require-ssl: true
- Let’s use our new application.yml as an external file:
java -Dspring.config.location=file:/home/ubuntu/application.yml -jar springboot-https-0.0.1-SNAPSHOT.jar
- Visit https://nbodev.duckdns.org:8443/test, the certificate looks good now:
- All good it works !
Troubleshooting
- Make sure you have a version of java > 11.0.2 in case you are using JDK 11, I had this stack and took me time to realize that this bug exists for the 11.0.2 version:
Caused by: java.io.IOException: keystore password was incorrect
at java.base/sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:2116)
at java.base/sun.security.util.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:222)
at java.base/java.security.KeyStore.load(KeyStore.java:1479)
at org.apache.tomcat.util.security.KeyStoreUtil.load(KeyStoreUtil.java:67)
at org.apache.tomcat.util.net.SSLUtilBase.getStore(SSLUtilBase.java:217)
at org.apache.tomcat.util.net.SSLHostConfigCertificate.getCertificateKeystore(SSLHostConfigCertificate.java:206)
at org.apache.tomcat.util.net.SSLUtilBase.getKeyManagers(SSLUtilBase.java:283)
at org.apache.tomcat.util.net.SSLUtilBase.createSSLContext(SSLUtilBase.java:247)
at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:97)
... 26 common frames omitted
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.
... 35 common frames omitted
- Make sure the port 80 is open when you create your certificate.
- Make sure you set the right ownership for the certificate file and you do not leave it under the root user one.
Conclusion
- The Let’s Encrypt certificate is valid for 90 days, you need to run the command sudo certbot renew if you want to renew it, I’m sure you will find some info online related to that. You can also cron that command.
- If you restart the EC2 instance the Public IPv4 address will change, you will need to update DuckDNS.