I'm using Clojure (ring and compojure) to build a web app and I don't know where to start with https. I have a registration and login that will need to be secured, then once they're authenticated they'll need to stay in https.
I can't seem to find a good tutorial on setting up https in general or for a clojure app specifically.
I found this answer: How do you enable https and http->https redirects in ring / compojure
Does that mean I can write my compojure app as if there's no https and have nginx sit in front and take care of all that for me?
Yes, the standard procedure is to have nginx act as a reverse proxy in front of the ring based webapp. This is considered secure and it's easier to maintain because it's more standard. Every clojure based site I know about does it this way.
The reverse proxy approach does seem to be the most common option. However, as an 'experiment' I looked into using just ring, jetty compojure etc with https. It isn't that hard.
First you need to use keytool to generate a self signed certificate and install that into a new keystore. The keytool docs a pretty good and can walk you through this process. Note that if you use a self signed cert, you will need to add an exception rule with most browsers to say that you trust that certificate.
Copy the keystore file created into the root of your project tree
Update the jetty config parameters to specify ssl, ssl-port, keystore file and keystore password.
Add the ring/ring-defaults package to your project.clj
Add the wrap-defaults middleware with the secure-site-defaults configuration option to force https connections i.e. redirect http connections to https.
This is not the setup I would recommend for production use, but I found it simpler than also having to configure ngix when doing development etc. The hardest part was working through the keytool process. However, just following the docs and examples gives you enough provided you don't allow yoruself to be overwhelmed by all the options - just keep it simple.
Something like
keytool -genkeypair \
-keystore $SSL_DIR/$KS_NAME \
-keypass $PASSWORD \
-storepass $PASSWORD \
-keyalg RSA -keysize 2048 \
-alias root \
-ext bc:c \
-dname "$ROOT_CN"
echo "CA Key"
keytool -genkeypair \
-keystore $SSL_DIR/$KS_NAME \
-alias ca \
-ext bc:c \
-keypass $PASSWORD \
-keyalg RSA -keysize 2048 \
-storepass $PASSWORD \
-dname "$CA_CN"
echo "Server Key"
keytool -genkeypair \
-keystore $SSL_DIR/$KS_NAME \
-alias server \
-keypass $PASSWORD \
-storepass $PASSWORD \
-keyalg RSA -keysize 2048 \
-dname "$SERVER_CN"
echo "Root Cert"
keytool -keystore $SSL_DIR/$KS_NAME \
-storepass $PASSWORD \
-alias root \
-exportcert \
-rfc > $SSL_DIR/root.pem
echo "CA Cert"
keytool -storepass $PASSWORD \
-keystore $SSL_DIR/$KS_NAME \
-certreq \
-alias ca | keytool -storepass $PASSWORD \
-keystore $SSL_DIR/$KS_NAME \
-gencert \
-alias root \
-ext BC=0 \
-rfc > $SSL_DIR/ca.pem
echo "Import CA cert"
keytool -keystore $SSL_DIR/$KS_NAME \
-storepass $PASSWORD \
-importcert \
-alias ca \
-file $SSL_DIR/ca.pem
echo "Server Cert"
keytool -storepass $PASSWORD \
-keystore $SSL_DIR/$KS_NAME \
-certreq \
-alias server | keytool -storepass $PASSWORD \
-keystore $SSL_DIR/$KS_NAME \
-gencert \
-alias ca \
-rfc > $SSL_DIR/server.pem
echo "Import Server Cert"
cat $SSL_DIR/root.pem $SSL_DIR/ca.pem $SSL_DIR/server.pem | \
keytool -keystore $SSL_DIR/$KS_NAME \
-storepass $PASSWORD \
-keypass $PASSWORD \
-importcert \
-alias server
Usually application-level code does not care whether it's HTTPS or HTTP. Most often it's provided by application server or a proxy in front of it. If you're familiar, your choices are pretty much the same as in the Java world.
If you're using the Jetty adapter, it has ssl? and ssl-port options. See docs here and related blog post here.
You can run your Ring app in plain HTTP and place a proxy such as Nginx or Apache in front of it. Proxy would implement HTTPS and forward requests as plain HTTP to your app.
Another solution is to try nginx-clojure (http://nginx-clojure.github.io) by which you can deploy ring app on nginx directly. nginx can do https things more efficiently.
Here's an example about nginx.conf
http {
### jvm dynamic library path
jvm_path '/usr/lib/jvm/java-7-oracle/jre/lib/amd64/server/libjvm.so';
### my app jars e.g. clojure-1.5.1.jar , groovy-2.3.4.jar ,etc.
jvm_var my_other_jars 'my_jar_dir/clojure-1.5.1.jar';
### my app classpath, windows user should use ';' as the separator
jvm_options "-Djava.class.path=jars/nginx-clojure-0.3.0.jar:#{my_other_jars}";
server {
listen 80 default deferred;
server_name example.com;
###redirect to https
rewrite ^/(.+) https://example.com/$1 permanent;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /opt/mycert/my-unified.crt;
ssl_certificate_key /opt/mycert/my.key;
location /myapp {
content_handler_name 'my/ringapp';
}
}
}
You can get more details about SSL server configurations from http://nginx.org/en/docs/http/configuring_https_servers.html
You can setup Ring middleware that will force all requests to use SSL. An easy way to do this is to use the ring-defaults library.
From the ring-defaults documentation:
There are four configurations included with the middleware
- api-defaults
- site-defaults
- secure-api-defaults
- secure-site-defaults
...
The "secure" defaults force SSL. Unencrypted HTTP URLs are redirected to the equivlant HTTPS URL, and various headers and flags are sent to prevent the browser sending sensitive information over insecure channels.
Also note:
In order to enable local development without SSL, you can set which ring-defaults set you use based on an environment variable. Environ helps with this.
In order to enable SSL to work behind a load balancer or proxy, you have to set :proxy to true in the ring-defaults map.
Here's the general idea:
(ns my-app
(:require [ring.middleware.defaults :refer :all]
[environ.core :refer [env]]))
(defroutes app-routes
(GET "/" [] "home page"))
(def app
(-> app-routes
(wrap-defaults (if (= (env :environment) "dev")
site-defaults
(assoc secure-site-defaults :proxy true))))
I don't know about nginx, but I have this working for an app deployed to Heroku.
Its very simple. If you want to enable HTTPS support in your web app, just do the following:
Generate a Java KeyStore(.jks) file using a linux tool called keytool.
In the ring map of your project.clj file, add the following:
{
:ssl? true
:ssl-port 8443
:keystore "path to the jks file"
:key-password "the keystore password"
}
Fire up the server. Now your web app is HTTPS enabled.
I had a similar problem while I was trying to test my Sign-In using Social Media code which obviously had to authenticate over HTTPS and this did the trick for me.
Related
In windows, when I install and run thinkorswim inside company network (with its own self-signed ssl cert), it cannot connect via https to tdameritrade's server. How do I update tos's java runtime with the self-signed cert?
open command prompt in windows and run the following command to install company's cacert into thinkorswim's jre:
cd C:\<thinkorswim-install-dir>\jre\bin\
keytool.exe -import -trustcacerts -noprompt -storepass changeit -alias mycertificate -keystore ..\lib\security\cacerts -file c:\mycert.cer
For MacOS
cd /Applications/thinkorswim/.install4j/jre.bundle/Contents/Home/jre/bin
./keytool -import -trustcacerts -noprompt -storepass changeit -alias mycertificate -keystore ../lib/security/cacerts -file /path/to/mycert.cer
I've a web server running on an ec2-instance which internally calls a REST server that is built using Spring Boot. Now, I am trying to get this REST server running under SSL. Here's what I've done so far:
1) Created a CSR & a key file using this command
openssl req -newkey rsa:2048 -nodes -keyout mydomain.key -out mydomain.csr
2) Copied 'csr' to get SSL certificate from GoDaddy.
3) Successfully installed the certificate under Nginx on my ec2-instance.
4) When I hit the home page under https, it works. I no longer get 'Not secure' message from the browser.
5) Login fails because it makes a REST call but REST server is not running under SSL so I am trying to get it running under SSL.
6) Ran following commands:
keytool -import -alias mydomain -keystore tomcat.keystore -trustcacerts -file mydomain.com.chained.crt
keytool -import -alias mydomain-key -keystore tomcat.keystore -trustcacerts -file mydomain.key
The previous command gives me an error message:
"keytool error: java.lang.Exception: Input not an X.509 certificate"
But this was the one created in step 1 above & the same file works under Nginx. What am I missing (other than the fact that I know very little about setting up SSLs!)? I need the second command to specify the value of 'server.ssl.keyAlias' in application.properties, I believe.
Not really an answer but overflowed comment.
You don't need to 'generate' an X.509 cert; you already got that from GoDaddy. If (and only if) the SpringBoot server is accessed by the same name(s) as (external) nginx -- which is unclear to me -- you need to convert the pair of private key AND certificate CHAIN from PEM format to a format Java uses. See:
How to import an existing x509 certificate and private key in Java keystore to use in SSL?
How can I set up a letsencrypt SSL certificate and use it in a Spring Boot application?
How to use .key and .crt file in java that generated by openssl?
Importing the private-key/public-certificate pair in the Java KeyStore
maybe Import key and SSL Certificate into java keystore
Thanks #Dave_thompson_085. Following 2 commands did the trick!
openssl pkcs12 -export -in mydomain.com.chained.crt -inkey mydomain.key -out keystore.p12 -name my-alias -caname root
keytool -importkeystore -deststorepass mypassword -destkeypass mypassword -destkeystore keystore.jks -srckeystore keystore.p12 -srcstoretype PKCS12 -srcstorepass mypassword -alias my-alias
and then in the application.properties I specified following properties:
server.port=8443
server.ssl.enabled=true
security.require-ssl=true
server.ssl.key-store=/etc/nginx/ssl/keystore.jks
server.ssl.key-store-password=mypassword
server.ssl.keyStoreType=JKS
server.ssl.keyAlias=my-alias
The Error:
javax.net.ssl.SSLException: Certificate for <localhost> doesn't match any of the subject alternative names: [xxxxxxx.xxx.xxxxxx.xxx]
I have a Spring Boot App running in my localhost. I also have a tunnel ssh via putty to a server.
Things I have done:
I manually created/imported keys/certificates of all ways.
I used -ext from keytool to add the dns addr and the localhost to SAN.
I also used a openSource java file to install the cert.
I changed the hosts files to: 127.0.0.1 xxx.xxx.xxxxxxx.xxx (if I ping the dns name it responds to the localhost address)
I used the VM Arguments -Djavax.net.debug=ssl to check if the certs are loading properly.
What am I missing? BTW, I'm also using a VPN.
You need to provide localhost as a subject alternative name when creating your certificate. You can do that by provide the following additional parameter: -ext "SAN:c=DNS:localhost,IP:127.0.0.1"
So something like this:
keytool -genkeypair -keyalg RSA -keysize 2048 -alias stackoverflow \
-dname "CN=stackoverflow,OU=Hakan,O=Hakan,C=NL" \
-ext "SAN:c=DNS:localhost,IP:127.0.0.1" -validity 3650 \
-storepass <password> -keypass <password> \
-keystore identity.jks -deststoretype pkcs12
Some explanation:
The SAN field will be used to match the hostname, which will be provided in the request. So when you are running your application on localhost, lets say https://localhost:443, and you also want to make a request to that specific host, then that hostname should also be available within the SAN field; otherwise, it will fail during the handshake process.
Let's grab Stackoverflow as an example. To be able to reach stackoverflow over https we would expect that the certificate should contain at least an entry of stackoverflow.com
Below is the certificate SAN value of stackoverflow with the specific DNS highlighted for this example:
As you can see already it contains also other DNS values. In this way websites owners can use the same certificate for multiple websites/subdomains etc.
I'm trying to create an installation script, that reads data out of an .ini file and writes it into several configuration files. One step is to create an SSL key via keytool and to request it's certification (create a csr-file). I now have the problem that keytool asks for the password instead of using the provided one (in the variable $keyStorePassword), although i also have activated -noprompt. I would be glad if anyone could help.
keytool -genkey -noprompt -keyalg RSA -keysize 2048 \
-dname "CN=${HOSTNAME}-${microserviceName}-${environment}, O=${organizationName}, L=${localityName}, S=${stateName}, C=${country}" \
-validity ${validity} -keypass ${keyStorePassword} -keystore ${keyStorePath} -alias ${microserventerviceName}-ssl
keytool -certreq -keyalg RSA -file ${microserviceName}.csr -keystore ${keyStorePath} -alias ${microserviceName}-ssl
I've already searched the web but the problem doesn't seem to be common.
There are two parameters for passwords:
-keypass: The password for the key.
-storepass: The password for the keystore.
Each key entry in a Java keystore has its individual password, so you have to provide both the keystore password and the key password when generating a new key or accessing a key.
I am new to https configuration in tomcat. I googled a lot but that did not solve my problem.
I have an ec2 machine and tomcat deployed on it. I tried to create self signed certificate , which was easy to generate.I used my domain www.test.com(changed test with my server name) and keystore file was generated. Then I edited conf/server.xml for https configuration as described in ssl howto of tomcat docs.But https is not working. Though when I do "curl https:// localhost :8443 -k " I can get response but it is not working in browser. Takes to long to respond and boom I am done with nothing. Do I need a certificate or It will work with keystore file only? Please tell me .
Please help me out
Thanks
Hi Thanks for you answer, I found my issue and would like explain my resolution
1.) Curl was working but it was not responding in browser . I had to open port 8443 manually from security groups in aws. It was working then :).
2.) For all others if they face with the problem "Failed to establish chain from reply"
i) please check your keystore file. Is it the same used to generate CSR?
ii) Install intermediate certificates provided by your certificate authority.
For some of them you can follow
https://www.sslcertificaten.nl/download/Root_Certificaten/
use alias primary and secondary respectively
$JAVA_HOME/bin/keytool -import -alias primary -keystore sslkey -trustcacerts -file primary_cert.crt -storepass mypass
$JAVA_HOME/bin/keytool -import -alias secondary -keystore sslkey -trustcacerts -file secondary_cert.crt -storepass mypass
iii) finally import your certificate
$JAVA_HOME/bin/keytool -import -alias tomcat -keystore sslkey -trustcacerts -file www_mydomain_com.p7b -storepass mypass
Hope this helps
cheers
If curl works, then the problem is either in the browser or in the servlet - SSL setup should be correct. Check the log files of Tomcat for errors and set breakpoints in your servlets to see what happens.