Spring Boot SSL Configuration Error: Private key must be accompanied by certificate chain

I’m working on a Spring Boot project and trying to configure SSL using a self-signed certificate. I generated the certificate using the following command:

keytool -genkeypair -alias local_ssl -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore local-ssl.p12 -validity 365 -ext san=dns:localhost -dname "CN=localhost, OU=TI, O=Eickrono, L=São Paulo, ST=SP, C=BR"

I followed these steps to generate and configure the SSL certificate for my Spring Boot application:

  1. Generated a self-signed certificate using keytool:
keytool -genkeypair -alias local_ssl -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore local-ssl.p12 -validity 365 -ext san=dns:localhost -dname "CN=localhost, OU=TI, O=Eickrono, L=São Paulo, ST=SP, C=BR"
  1. Created a Certificate Signing Request (CSR) using keytool:
keytool -certreq -alias local_ssl -keystore local-ssl.p12 -file local_ssl.csr
  1. Created a temporary CA and signed the CSR using OpenSSL:
openssl genpkey -algorithm RSA -out myCA.key -pkeyopt rsa_keygen_bits:2048
openssl req -x509 -new -nodes -key myCA.key -sha256 -days 365 -out myCA.pem -subj "/CN=MyCA"
openssl x509 -req -in local_ssl.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out local_ssl.crt -days 365 -sha256
  1. Imported the CA certificate and the signed certificate back into the keystore:
keytool -import -alias myCA -file myCA.pem -keystore local-ssl.p12 -trustcacerts
keytool -import -alias local_ssl -file local_ssl.crt -keystore local-ssl.p12
  1. Updated my application.yml to use the keystore:
server:
  port: 8443
  ssl:
    enabled: true
    key-alias: local_ssl
    key-store: classpath:local-ssl.p12
    key-store-type: PKCS12
    key-password: 20071991
  1. Copied the local-ssl.p12 file to the src/main/resources directory of my project. Expected Result: I expected my Spring Boot application to start successfully with SSL enabled, using the self-signed certificate I generated.

Actual Result: When I run my application, I encounter the following errors:

2024-09-16T15:43:44.975-03:00 INFO [35m35108[0;39m [2m---[0;39m [2m[login-usuario-jwt.jar] [           main][0;39m [2m[0;39m[36m.s.b.a.l.ConditionEvaluationReportLogger[0;39m [2m:[0;39m 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
[2m2024-09-16T15:43:44.998-03:00[0;39m [31mERROR[0;39m [35m35108[0;39m [2m---[0;39m [2m[login-usuario-jwt.jar] [           main][0;39m [2m[0;39m[36mo.s.boot.SpringApplication              [0;39m [2m:[0;39m Application run failed

org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:288) ~[spring-context-6.1.12.jar:6.1.12]
    at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:469) ~[spring-context-6.1.12.jar:6.1.12]
    at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
    at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:257) ~[spring-context-6.1.12.jar:6.1.12]
    at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:202) ~[spring-context-6.1.12.jar:6.1.12]
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:990) ~[spring-context-6.1.12.jar:6.1.12]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:628) ~[spring-context-6.1.12.jar:6.1.12]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.3.3.jar:3.3.3]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.3.jar:3.3.3]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.3.jar:3.3.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.3.jar:3.3.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.3.jar:3.3.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.3.jar:3.3.3]
    at com.eickrono.LoginUsuarioJWT.main(LoginUsuarioJWT.java:21) ~[classes/:na]
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat server
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:251) ~[spring-boot-3.3.3.jar:3.3.3]
    at org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle.start(WebServerStartStopLifecycle.java:44) ~[spring-boot-3.3.3.jar:3.3.3]
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:285) ~[spring-context-6.1.12.jar:6.1.12]
    ... 13 common frames omitted
Caused by: java.lang.IllegalArgumentException: standardService.connector.startFailed
    at org.apache.catalina.core.StandardService.addConnector(StandardService.java:222) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.addPreviouslyRemovedConnectors(TomcatWebServer.java:310) ~[spring-boot-3.3.3.jar:3.3.3]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:236) ~[spring-boot-3.3.3.jar:3.3.3]
    ... 15 common frames omitted
Caused by: org.apache.catalina.LifecycleException: Protocol handler start failed
    at org.apache.catalina.connector.Connector.startInternal(Connector.java:1061) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.StandardService.addConnector(StandardService.java:219) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    ... 17 common frames omitted
Caused by: java.lang.IllegalArgumentException: Private key must be accompanied by certificate chain
    at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:114) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.AbstractJsseEndpoint.initialiseSsl(AbstractJsseEndpoint.java:70) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:199) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.AbstractEndpoint.bindWithCleanup(AbstractEndpoint.java:1304) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.AbstractEndpoint.start(AbstractEndpoint.java:1390) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.coyote.AbstractProtocol.start(AbstractProtocol.java:643) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.connector.Connector.startInternal(Connector.java:1058) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    ... 19 common frames omitted
Caused by: java.lang.IllegalArgumentException: Private key must be accompanied by certificate chain
    at java.base/java.security.KeyStore.setKeyEntry(KeyStore.java:1188) ~[na:na]
    at org.apache.tomcat.util.net.SSLUtilBase.getKeyManagers(SSLUtilBase.java:405) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.SSLUtilBase.createSSLContext(SSLUtilBase.java:268) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:112) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    ... 25 common frames omitted

I’m not sure what I’m missing or doing wrong. How can I properly configure SSL in my Spring Boot application to avoid these errors?

C:\backup\Projetos\SSL>keytool -list -v -keystore local-ssl.p12 Enter keystore password:

Keystore type: PKCS12 Keystore provider: SUN

Your keystore contains 2 entries

Alias name: local_ssl Creation date: 16 de set. de 2024 Entry type: PrivateKeyEntry Certificate chain length: 2 Certificate[1]: Owner: CN=localhost, OU=TI, O=Eickrono, L=São Paulo, ST=SP, C=BR Issuer: CN=MyCA Serial number: 269b5287a49a6f3db26782514442c87d7dc82554 Valid from: Mon Sep 16 15:14:45 ART 2024 until: Tue Sep 16 15:14:45 ART 2025 Certificate fingerprints: SHA1: 9F:51:27:33:E4:5B:D4:0E:2F:F0:A1:F3:D1:9C:A4:21:9F:E6:7B:45 SHA256: D7:7A:42:92:7A:59:98:D3:3C:82:78:52:74:62:4B:6E:BD:C1:6B:9E:44:CD:92:C7:C5:85:D1:C6:B0:EA:54:C1 Signature algorithm name: SHA256withRSA Subject Public Key Algorithm: 2048-bit RSA key Version: 3

Extensions:

#1: ObjectId: 2.5.29.35 Criticality=false AuthorityKeyIdentifier [ KeyIdentifier [ 0000: 42 15 8F 30 02 D7 49 06 81 FA 72 ED D4 6E 4E 9B B…0…I…r…nN. 0010: 3B 8E 99 D5
;… ] ]

#2: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 9E B9 DB 08 9D 1C 7C BF 54 04 9F F1 8E 0F 8E AE …T… 0010: D0 99 7B 94
… ] ]

Certificate[2]: Owner: CN=MyCA Issuer: CN=MyCA Serial number: 6ae26b43aa8c675e9cacc558ccbb6f811e75f5ff Valid from: Mon Sep 16 15:14:16 ART 2024 until: Tue Sep 16 15:14:16 ART 2025 Certificate fingerprints: SHA1: 85:17:2D:9C:CC:61:9A:65:3B:77:9E:08:6A:8F:6F:C6:AB:8D:E3:27 SHA256: 23:D6:D0:27:86:38:90:59:D1:FC:41:6E:0E:34:B3:BF:02:51:15:19:EC:7D:3A:AE:74:8F:1D:80:E5:80:2A:69 Signature algorithm name: SHA256withRSA Subject Public Key Algorithm: 2048-bit RSA key Version: 3

Extensions:

#1: ObjectId: 2.5.29.35 Criticality=false AuthorityKeyIdentifier [ KeyIdentifier [ 0000: 42 15 8F 30 02 D7 49 06 81 FA 72 ED D4 6E 4E 9B B…0…I…r…nN. 0010: 3B 8E 99 D5
;… ] ]

#2: ObjectId: 2.5.29.19 Criticality=true BasicConstraints:[ CA:true PathLen: no limit ]

#3: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 42 15 8F 30 02 D7 49 06 81 FA 72 ED D4 6E 4E 9B B…0…I…r…nN. 0010: 3B 8E 99 D5
;… ] ]



Alias name: myca Creation date: 16 de set. de 2024 Entry type: trustedCertEntry

Owner: CN=MyCA Issuer: CN=MyCA Serial number: 6ae26b43aa8c675e9cacc558ccbb6f811e75f5ff Valid from: Mon Sep 16 15:14:16 ART 2024 until: Tue Sep 16 15:14:16 ART 2025 Certificate fingerprints: SHA1: 85:17:2D:9C:CC:61:9A:65:3B:77:9E:08:6A:8F:6F:C6:AB:8D:E3:27 SHA256: 23:D6:D0:27:86:38:90:59:D1:FC:41:6E:0E:34:B3:BF:02:51:15:19:EC:7D:3A:AE:74:8F:1D:80:E5:80:2A:69 Signature algorithm name: SHA256withRSA Subject Public Key Algorithm: 2048-bit RSA key Version: 3

Extensions:

#1: ObjectId: 2.5.29.35 Criticality=false AuthorityKeyIdentifier [ KeyIdentifier [ 0000: 42 15 8F 30 02 D7 49 06 81 FA 72 ED D4 6E 4E 9B B…0…I…r…nN. 0010: 3B 8E 99 D5
;… ] ]

#2: ObjectId: 2.5.29.19 Criticality=true BasicConstraints:[ CA:true PathLen: no limit ]

#3: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 42 15 8F 30 02 D7 49 06 81 FA 72 ED D4 6E 4E 9B B…0…I…r…nN. 0010: 3B 8E 99 D5
;… ] ]

The error “Private key must be accompanied by certificate chain” indicates that your Spring Boot application cannot find a valid certificate chain associated with the private key in your keystore.

Here’s a step-by-step approach to troubleshoot and resolve the issue:

  1. Verify the Keystore Content

Run the following command to inspect your keystore and ensure that the local_ssl alias has a certificate chain:

bash

keytool -list -v -keystore local-ssl.p12

You should see an entry for local_ssl that includes a certificate chain length greater than 1. It seems from your output that it does have a certificate chain, but let’s confirm it’s structured correctly.
2. Ensure Proper Certificate Chain is Imported

When you imported the CA certificate and the signed certificate back into the keystore, it’s crucial that the CA certificate is correctly associated with the signed certificate. Here’s how you can ensure that:

Import CA Certificate: Make sure to import the CA certificate first before importing the signed certificate:

bash

keytool -import -alias myCA -file myCA.pem -keystore local-ssl.p12 -trustcacerts

Import Signed Certificate: After importing the CA, import the signed certificate. Use the -trustcacerts option here as well to make sure it’s linked correctly.

bash

keytool -import -alias local_ssl -file local_ssl.crt -keystore local-ssl.p12 -trustcacerts
  1. Check the Certificate Chain

You can also check if the chain is intact. The keytool command should show something like this under the local_ssl alias:

perl

Certificate chain length: 2

The first certificate should be your signed certificate, and the second should be your CA certificate.
4. Confirm Key Store Type and Configuration

Make sure your application.yml is correctly configured. Here’s a sample configuration:

yaml

server:
port: 8443
ssl:
enabled: true
key-store: classpath:local-ssl.p12
key-store-type: PKCS12
key-store-password: your_keystore_password
key-alias: local_ssl

  1. Ensure Correct Password

Ensure that the keystore password and key password are correct in your Spring Boot configuration. The key password can be different from the keystore password, but if it is, specify it using key-password:

yaml

key-password: your_key_password

  1. Recreate the Keystore

If the problem persists, it might be easier to recreate the keystore to ensure everything is in order:

Delete the Existing Keystore.
Regenerate the Self-Signed Certificate.
Re-import the CA and Signed Certificates.
  1. Debugging

Run your Spring Boot application with debugging enabled to gather more information:

bash

./mvnw spring-boot:run -Dspring-boot.run.arguments=–debug

This may provide more detailed logs about what’s going wrong during the SSL configuration.