From d160010c6bb0522d4fed45ba49e8ca340e60a3e3 Mon Sep 17 00:00:00 2001 From: Andreas Falk Date: Fri, 20 Jul 2018 16:34:24 +0200 Subject: [PATCH] update according to blog post --- .gitignore | 5 ++- README.md | 41 ++++++++++++++++++++-- build.gradle | 6 ++-- cert.pem | 29 --------------- setup_ca/ca.pem | 26 ++++++++++++++ setup_ca/setup_ca.cmd | 17 +++++++++ setup_ca/setup_ca.sh | 18 ++++++++++ src/main/resources/application.properties | 4 +-- src/main/resources/server.jks | Bin 0 -> 5997 bytes src/main/resources/ssl-certs.jks | Bin 6455 -> 0 bytes 10 files changed, 109 insertions(+), 37 deletions(-) delete mode 100644 cert.pem create mode 100644 setup_ca/ca.pem create mode 100644 setup_ca/setup_ca.cmd create mode 100644 setup_ca/setup_ca.sh create mode 100644 src/main/resources/server.jks delete mode 100644 src/main/resources/ssl-certs.jks diff --git a/.gitignore b/.gitignore index 2b1babb..d93ae47 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ /nbbuild/ /dist/ /nbdist/ -/.nb-gradle/ \ No newline at end of file +/.nb-gradle/ + +setup_ca/root-ca +setup_ca/server \ No newline at end of file diff --git a/README.md b/README.md index c7a1ff7..0d04e74 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,40 @@ -# ssl-demo +# SSL Demo Application -Accompanying project code for blog post on [blog.novatec-gmbh.de](https://blog.novatec-gmbh.de) +This is the accompanying project code for the blog post for _Secure Spring Boot Applications with TLS and HTTP/2_ +on [blog.novatec-gmbh.de](https://blog.novatec-gmbh.de). + +## System Requirements + +* Java 9 SDK + +## Using this demo project + +You can either use this project either + +1. directly without changing anything here. +The only thing you have to do is to import the root certificate in +your web browser as new authority. The root certificate can be found +in *setup_ca/ca.pem*. After importing this you can start the application and +navigate to [https://localhost:8443](https://localhost:8443) in your web browser. + +2. or with setting up a new private certificate authority first. Then you have to follow the +explanations of the next sections. + +## Setting up the private CA + +To setup the private certificate authority with root certificate and to create the valid +server certificate please run the corresponding shell script *setup_ca.sh* (linux) / *setup_ca.cmd* (windows) +in sub directory *setup_ca*. + +This script creates the required sub directories and performs all the steps using keytool +as described in the blog post. + +After executing the script you will find the important files here: + +* _setup_ca/root-ca_: Here you find the root certificate *ca.pem* that you have to import as new authority in your web browser +* _setup_ca/server_: Here you find the key store *server.jks* containing the root and server certificates. +This has to be copied to the directory *src/main/resources*. The existing key store can be overwritten. + +After you have completed all these steps you can start the application and +navigate to [https://localhost:8443](https://localhost:8443) in your web browser. + \ No newline at end of file diff --git a/build.gradle b/build.gradle index cb29f29..b546560 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { - kotlinVersion = '1.2.41' - springBootVersion = '2.0.2.RELEASE' + kotlinVersion = '1.2.51' + springBootVersion = '2.0.3.RELEASE' } repositories { mavenCentral() @@ -20,7 +20,7 @@ apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' group = 'com.example' -version = '0.0.1-SNAPSHOT' +version = '1.0.0-SNAPSHOT' sourceCompatibility = 1.8 compileKotlin { kotlinOptions { diff --git a/cert.pem b/cert.pem deleted file mode 100644 index 8f9da86..0000000 --- a/cert.pem +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE7TCCA1WgAwIBAgIJAObJfTkGW5U3MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD -VQQGEwJERTEbMBkGA1UECAwSQmFkZW4tV3VlcnR0ZW1iZXJnMSAwHgYDVQQHDBdM -ZWluZmVsZGVuLUVjaHRlcmRpbmdlbjETMBEGA1UECgwKTXkgQ29tcGFueTEUMBIG -A1UECwwLRGV2ZWxvcG1lbnQxEzARBgNVBAMMCk15IFJvb3QgQ0EwHhcNMTgwNjA4 -MjAxMTE1WhcNMjgwNjA1MjAxMTE1WjCBjDELMAkGA1UEBhMCREUxGzAZBgNVBAgM -EkJhZGVuLVd1ZXJ0dGVtYmVyZzEgMB4GA1UEBwwXTGVpbmZlbGRlbi1FY2h0ZXJk -aW5nZW4xEzARBgNVBAoMCk15IENvbXBhbnkxFDASBgNVBAsMC0RldmVsb3BtZW50 -MRMwEQYDVQQDDApNeSBSb290IENBMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB -igKCAYEAzGqpSbnnRFIf+iDKBcjfj7eXYL6vyekAvj1YAkRYaIQ8yo6tYYP0QTBw -EAo/Oh4PGY34JsTOV4CQOEY3R1v/KM9S3+SkXy3LKAhPEQbMMBq8oUws4p1PuKMc -EKSYQk0++v3BioxuYp8sen+bUA2CfxQ6vKHKLNxSdQvehdNDulckOcIAucqnDK35 -2H/oQmhacNlwTlTdEvTFpnGoL5rPkAE9Mf0/2uOMZ28LCRAuVm9SM1w9JBckbTAk -sXdb8QBwIuWrtgFdWu3R7scOduJiiQeA4JtdEGZgS0p2aj/oIBVt6qIZnbL126lH -UJFa6fBYnKJe2Klz1Vv68D1H4NduUYH05tuRB8YNPKWvTlRbf/v3SVb30KHr73ff -ZmYgcQL0ns8SUxfpcvDFgg/4wJqabPD2HB5254pQ/pKZA3LwG0xK+ws3pMlt0iKl -pamv67l+ZO26Z6+NunkxvCTJqT4L+Sdxm7EYk/NL8xHlIM7lZqSnmCnHxAXJIA05 -VXBS9SYBAgMBAAGjUDBOMB0GA1UdDgQWBBRd/2s6n2iL0PqpTfmNPIrA8Qz9WDAf -BgNVHSMEGDAWgBRd/2s6n2iL0PqpTfmNPIrA8Qz9WDAMBgNVHRMEBTADAQH/MA0G -CSqGSIb3DQEBCwUAA4IBgQCam7dEWSBA9U8qv4YxIIXYvT3GXrgtPL+KNPm4lpv0 -74bI4zbsgPzocTuoxMhcGJg21eqLd2PDTgxlRYLjmRALTjggV6OCqJDdBUAi4Wzy -lRzuF25OLVtvXJUQIIg1hvtGj701PnAJt91aTvWZ31kG3CloeGu/5R1O3ebbykRj -OcmqIeu5T8TCFJqroYbi+s2hRMQKcl86iX8NEwn6zgkAZyPyhmKU0YHAyU36kH71 -LMLDQxZgo7tL5yT0oXNLVS7x3zGko6wlOMO/ZvYW+g/veJyH9r7QyK1CsCO48x2T -+SpsdFwtKHj1TbNtzi7Co3aw+6tzdTfeFIidmXEdTOagOBVYtYdgeWZQkCBHmabB -d5x5SXJThjAwKK3Dsd0KB633dNzPcgHf1SdXM7pu3CKuZ6N9ya6peVtqfx8r+89x -+2j9wbSqQ+a4VOSlTIvH4J4i50T5FbKf/Ayjj1azmqxP+g4s2l86bCdjVWd/XE+p -ytRbhJnanj4IYEUis3we3nc= ------END CERTIFICATE----- diff --git a/setup_ca/ca.pem b/setup_ca/ca.pem new file mode 100644 index 0000000..cbeda63 --- /dev/null +++ b/setup_ca/ca.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEYTCCAsmgAwIBAgIELD51oTANBgkqhkiG9w0BAQsFADBSMQswCQYDVQQGEwJE +RTEYMBYGA1UEChMPTXkgT3JnYW5pemF0aW9uMRQwEgYDVQQLEwtEZXZlbG9wbWVu +dDETMBEGA1UEAxMKTXkgUm9vdCBDQTAeFw0xODA3MjAxMzQ4MThaFw0yODA3MTcx +MzQ4MThaMFIxCzAJBgNVBAYTAkRFMRgwFgYDVQQKEw9NeSBPcmdhbml6YXRpb24x +FDASBgNVBAsTC0RldmVsb3BtZW50MRMwEQYDVQQDEwpNeSBSb290IENBMIIBojAN +BgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnfhwA0qmHtGK2UCFUakAkX9nypx5 +pIfDIX7q3AlGek1RoPmbzLRpyAA5irsSqcWfQW8j+djPMpjaNtB/l6RBSkSQLBgn +T3/01SZHtVtQ4d3Vq1TAU11HaR4xOmNB/4z86e+roeoNoqpcL3AIv5lwWfLlyRmp +iNVzFkKr6FFac5yFNsaU4GUOtDAdB5kXjg9ayksRuI3sRCpJbzYSTUz5xf27M6+Q +h50tvztXNrEXHaLlnZgHYvNIyO07G45UtNW24newkV8LWH/0Rj71IdauezbaYs/I +F2ID/cio/AsdxR5lBurvGdBJpIiJhuMGMP+xOaau8TDJEHcsBujT1kCa/Mq27qCF +XoSxcGCvAv7YkaPuS44znh76vnBT7lRb+B8fs9F9P4OhAfgAo2wHlAWmGz3g/RHa +xyrbTLd4YlWb9yBjPYhkWJLfi1s+TFC7VbEK42x284l/Q1TklVC2S7Hh0fU8wH51 +Y4Jl0OTLNAMrsNfDVxqf5HnPj+oA47cg4rAZAgMBAAGjPzA9MB0GA1UdDgQWBBSJ +zBTaqcLIWK/MENQe7PAqiPo+azALBgNVHQ8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAYEAjTrajAloePQOpKrxQ61qGNdtMnxGGExji5FF +lAnhtKzZsDuOkMhc7LRn1LIT+ugWFmbLPxQ6Uq71xg3QskWADJ1bV4Wex5J7RDar +8v4z41kILa+XcdNVQHq3Fujox7IYVt4U5wZB4sXQJUK6ELso4L8I/jMgx/GmDfFC +e8ltSJDErvlEMRmu3bfdaNieOfhB5Soa9WUzm7a8Z8BUXwOMelk9P24B3HdpDWcr +9Ud3+PCWmxI+lB33l6O3EFEdGqySd2q3cn8R89nqokO8ctGYmm00EBPoy+VfdlAy +9NS5KvWS9wxWpFuvBQzWVc8buQkspBKoYC8NAnFcdM3FwUf/uRVCNYO8V6mgKOUU +64RMQHzhSygFOCI4A3T6S9lwkpCKoOEhIZnSmCfqCMFjo0ilFWCQ3E1TBSAtEPfW +z4xR/pSNZ5NIIvc8Bfdk0R/7Jah0oOrECe4hlD7GoVbOLRwjHKzEvkgmttGLL/tW +whnMnXLKCaXDq6y1JWrjth9+zPSa +-----END CERTIFICATE----- diff --git a/setup_ca/setup_ca.cmd b/setup_ca/setup_ca.cmd new file mode 100644 index 0000000..3f89764 --- /dev/null +++ b/setup_ca/setup_ca.cmd @@ -0,0 +1,17 @@ + +md root-ca +md server + +keytool -genkeypair -keyalg RSA -keysize 3072 -alias root-ca -dname "CN=My Root CA,OU=Development,O=My Organization,C=DE" -ext BC:c=ca:true -ext KU=keyCertSign -validity 3650 -keystore root-ca\ca.jks -storepass secret -keypass secret + +keytool -exportcert -keystore root-ca\ca.jks -storepass secret -alias root-ca -rfc -file root-ca\ca.pem + +keytool -genkeypair -keyalg RSA -keysize 3072 -alias localhost -dname "CN=localhost,OU=Development,O=My Organization,C=DE" -ext BC:c=ca:false -ext EKU:c=serverAuth -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -validity 3650 -keystore server\server.jks -storepass secret -keypass secret + +keytool -certreq -keystore server\server.jks -storepass secret -alias localhost -keypass secret -file server\server.csr + +keytool -gencert -keystore root-ca\ca.jks -storepass secret -infile server\server.csr -alias root-ca -keypass secret -ext BC:c=ca:false -ext EKU:c=serverAuth -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -validity 3650 -rfc -outfile server\server.pem + +keytool -importcert -noprompt -keystore server\server.jks -storepass secret -alias root-ca -keypass secret -file root-ca\ca.pem +keytool -importcert -noprompt -keystore server\server.jks -storepass secret -alias localhost -keypass secret -file server\server.pem + diff --git a/setup_ca/setup_ca.sh b/setup_ca/setup_ca.sh new file mode 100644 index 0000000..57d300b --- /dev/null +++ b/setup_ca/setup_ca.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +mkdir root-ca +mkdir server + +keytool -genkeypair -keyalg RSA -keysize 3072 -alias root-ca -dname "CN=My Root CA,OU=Development,O=My Organization,C=DE" -ext BC:c=ca:true -ext KU=keyCertSign -validity 3650 -keystore ./root-ca/ca.jks -storepass secret -keypass secret + +keytool -exportcert -keystore ./root-ca/ca.jks -storepass secret -alias root-ca -rfc -file ./root-ca/ca.pem + +keytool -genkeypair -keyalg RSA -keysize 3072 -alias localhost -dname "CN=localhost,OU=Development,O=My Organization,C=DE" -ext BC:c=ca:false -ext EKU:c=serverAuth -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -validity 3650 -keystore ./server/server.jks -storepass secret -keypass secret + +keytool -certreq -keystore ./server/server.jks -storepass secret -alias localhost -keypass secret -file ./server/server.csr + +keytool -gencert -keystore ./root-ca/ca.jks -storepass secret -infile ./server/server.csr -alias root-ca -keypass secret -ext BC:c=ca:false -ext EKU:c=serverAuth -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -validity 3650 -rfc -outfile ./server/server.pem + +keytool -importcert -noprompt -keystore ./server/server.jks -storepass secret -alias root-ca -keypass secret -file ./root-ca/ca.pem +keytool -importcert -noprompt -keystore ./server/server.jks -storepass secret -alias localhost -keypass secret -file ./server/server.pem + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a5e49d7..52e1426 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,8 +1,8 @@ server.port=8443 server.ssl.enabled=true -server.ssl.key-store=classpath:ssl-certs.jks +server.ssl.key-store=classpath:server.jks server.ssl.key-store-type=PKCS12 server.ssl.key-store-password=secret -server.ssl.key-alias=server +server.ssl.key-alias=localhost server.ssl.key-password=secret server.http2.enabled=true diff --git a/src/main/resources/server.jks b/src/main/resources/server.jks new file mode 100644 index 0000000000000000000000000000000000000000..3beb6ff17598b027045fd93d313a0cb4c9fede95 GIT binary patch literal 5997 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