smeeb
smeeb

Reputation: 29567

Spring Boot causes SSL peer handshake failure with self-signed certs

I have created a GitHub project that perfectly reproduces everything explained here.


I am building a Spring Boot app (with an embedded Jetty web container) and am trying to get it to serve self-signed OpenSSL certs when running locally (when running on staging or prod environments, the app will be serving a root CA-signed cert).

So to create the SSL capabilities, first I created the public key/CSR by issuing:

openssl req -x509 -newkey rsa:4096 -keyout myapp-key.pem -out myapp-csr.pem -days 3650

Then I created the JKS and imported my public key like so:

keytool -importcert -trustcacerts -file myapp-csr.pem -alias myorg -keystore myapp.jks

I then updated my application.yml with SSL properties:

ssl:
  key-store: 'myapp.jks'
  key-store-password: '123456'
  key-password: '123456' 

I then run my Spring Boot app:

./gradlew build && java -Dspring.config=. -jar build/libs/spring-boot-troubleshooting.jar

So far so good -- no errors/exceptions/warnings at startup. I then open a new terminal and run:

curl -k -H "Content-Type: application/json" -X GET https://localhost:9200/health
curl: (35) SSL peer handshake failed, the server most likely requires a client certificate to connect

If I switch back over to the terminal where the app is running, I now see this in the console output:

06:14:00.149 [qtp1706099897-15] WARN  org.eclipse.jetty.http.HttpParser - Illegal character 0x16 in state=START for buffer HeapByteBuffer@5b876b3[p=1,l=175,c=8192,r=174]={\x16<<<\x03\x01\x00\xAa\x01\x00\x00\xA6\x03\x03ZL\xBa\xF8T\x86r...\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x00\x17\x00\x00>>>-Type: applicatio...\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00}

The -k is to intentional ignore SSL since this is a self-signed cert. I've tried both with and without -k and the result is the same.

I see evidence that this could be a MacOS issue for some reason, but frankly I'm not even sure where to start looking. Something wrong with the way I created the public/private key pair? Something wrong with the way I imported the public key/CSR into the JKS file? Something else wrong with any aspect of my configuration?

Upvotes: 1

Views: 4393

Answers (3)

Mehmet Sunkur
Mehmet Sunkur

Reputation: 2423

@EJP explained root cause well. There is no private key in your myapp.jks file.

I can tell the solutions detailed. There are 3 solution. I tested them by your code and java 8.

  1. only keytool - fastest one

    remove existing myapp.jks and regenerate by keytool with these parameters.

    keytool -genkeypair -dname "cn=Name Surname, ou=MyUnit, o=MyOrg, c=US" -alias myorg -keypass 123456 -storepass 123456 -validity 365 -keyalg RSA -keystore myapp.jks

  2. only openssl

    Create pkcs file by openssl named "myapp.p12".

    openssl pkcs12 -inkey myapp-key.pem -in myapp-csr.pem -export -out myapp.p12;

    change application.yml key-store: 'myapp.jks' to key-store: 'myapp.p12'

  3. combination of them(current method).

    Create pkcs file by openssl named "myapp.p12".

    openssl pkcs12 -inkey myapp-key.pem -in myapp-csr.pem -export -out myapp.p12;

    import it into jks file

    keytool -importkeystore -srckeystore myapp.p12 -srcstoretype pkcs12 -destkeystore myapp.jks -deststoretype JKS

Upvotes: 1

Mikita Harbacheuski
Mikita Harbacheuski

Reputation: 2253

To perform the SSL handshake server keystore must contain both public certificate and private key. The easiest way to do it is to create a p12 bundle with private key and certificate and import both of them at once using importkeystore command.

openssl req -x509 -newkey rsa:4096 -keyout myapp-key.pem -out myapp-csr.pem -days 3650 -subj '/CN=localhost'
openssl pkcs12 -export -in myapp-csr.pem -inkey myapp-key.pem -out keystore.p12
keytool -importkeystore -deststorepass 123456 -destkeystore keystore.jks -srckeystore keystore.p12 -srcstoretype PKCS12

Now you will be able to query your server using curl.

curl -k -H "Content-Type: application/json" -X GET https://localhost:9200/health
curl --cacert myapp-csr.pem -H "Content-Type: application/json" https://localhost:9200/health

So basically your configuration is correct except for missing private key in keystore.

Upvotes: 1

user207421
user207421

Reputation: 311023

None of this makes sense.

  • You don't need to use openssl at all.
  • A CSR is not a certificate, let alone a public key.
  • The result of importing a CSR into a KeyStore is not a valid KeyStore, as there is no private key.

You need to use the keytool, and nothing else, as follows:

  1. keytool -genkey ...
  2. keytool -selfcert ...

    using the same alias both times.

  3. keytool -export ...

  4. keytool -import ... into the clients' truststore file(s).

Upvotes: -1

Related Questions