#!/bin/sh # Demonstrate using wildcard certs with nginx, wget, curl, and apt # This exposes a problem on some systems (e.g. clean containers), # or maybe only shows that the DNS name in the cert must be real and match. # Prerequisites on ubuntu (beyond the obvious): # sudo apt-get install gnutls-bin bind9-host nginx curl wget reprepro # sudo gem install fpm set -e set -x # Get FQDN hostname=`hostname` # In case system isn't configured to fess up the hostname, see if 'host' will look it up hostname=`host $hostname | head -n1 | awk '{print $1}'` dpkgarch=`dpkg --print-architecture` distro=`lsb_release -c -s` generate_fake_wildcard_cert() { rm -f server.crt server.key ca.crt # Oddly, to create wildcard certs with openssl, you have to put it in openssl.cnf. sed \ -e 's,# Extensions to add to a certificate request,subjectAltName = @alt_names,' \ -e 's,# req_extensions = v3_req,req_extensions = v3_req,' \ < /etc/ssl/openssl.cnf > my-openssl.cnf # And you also need some of it in a separate file. Sheesh. cat > alt-names.cnf <<_EOF_ [foosan] subjectAltName = @alt_names [alt_names] DNS.1 = $hostname DNS.2 = *.$hostname _EOF_ cat alt-names.cnf >> my-openssl.cnf # Create CA openssl genrsa 2048 -nodes > ca.key openssl req -new -x509 -days 3650 -subj "/C=US/ST=California/L=Los Angeles/CN=ca-$hostname" -key ca.key -out ca.crt # Create server cert request openssl genrsa 2048 -nodes > server.key openssl req -new -sha256 -subj "/C=US/ST=California/L=Los Angeles/CN=$hostname" -key server.key -out server.csr -config my-openssl.cnf # Verify the request has the wildcard if ! openssl req -in server.csr -text -noout | egrep "X509v3 Subject Alternative Name|DNS:" then echo "FAIL: csr didn't contain the subjectAltName" exit 1 fi # Create server cert per the request # Note: There is an 'openssl ca' command which might be nicer openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt -extfile alt-names.cnf -extensions foosan # Verify the cert has the wildcard if ! openssl x509 -in server.crt -text -noout | egrep "X509v3 Subject Alternative Name|DNS:" then echo "FAIL: crt didn't contain the subjectAltName" exit 1 fi openssl verify -CAfile ca.crt server.crt # Create hosts entry for temporary server if ! grep foo.$hostname /etc/hosts then echo "127.0.0.1 foo.$hostname" | sudo tee -a /etc/hosts fi # Create temporary server killall gnutls-serv || true gnutls-serv \ --x509keyfile server.key \ --x509certfile server.crt \ --x509cafile ca.crt > serv.log 2>&1 & sleep 1 # Connect to temporary server and verify its certificate # First verify using openssl openssl s_client -showcerts -connect foo.$hostname:5556 -CAfile ca.crt < /dev/null > s_client.log cat s_client.log if ! openssl x509 -noout -text < s_client.log | egrep "X509v3 Subject Alternative Name|DNS:" then echo "FAIL: crt didn't contain subjectAltName" exit 1 fi # Then verify using gnutls. # On some systems, this fails, saying # - Status: The certificate is NOT trusted. The name in the certificate does not match the expected. # The system this fails on for me is a throwaway container without a real FQDN. # Does gnutls distrust servers without FQDN's? gnutls-cli --x509cafile ca.crt foo.$hostname -p 5556 < /dev/null killall gnutls-serv || true } # Create extra.conf before calling nginx_create() { # Now set up a web server using this cert ps_confdir=`pwd` ps_webroot=`pwd`/www rm -rf $ps_webroot mkdir $ps_webroot cat > nginx.conf <<_EOF_ server { server_name $hostname; root $ps_webroot; # Require TLS listen 443; ssl on; ssl_certificate $ps_confdir/server.crt; ssl_certificate_key $ps_confdir/server.key; #ssl_client_certificate $ps_confdir/client-ca.crt; #ssl_verify_depth 2; #ssl_verify_client on; # See https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html # Require *modern* TLS ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Requre strong ciphers ssl_ciphers 'AES128+EECDH:AES128+EDH:!aNULL'; ssl_prefer_server_ciphers on; # FIXME: enable this # OCSP stapling not supported in older nginx # See https://raymii.org/s/tutorials/OCSP_Stapling_on_nginx.html #ssl_stapling on; #ssl_stapling_verify on; #resolver 8.8.4.4 8.8.8.8 valid=300s; #resolver_timeout 10s; # Generate with # cd /etc/ssl/certs # openssl dhparam -out dhparam.pem 4096 ssl_dhparam /etc/ssl/certs/dhparam.pem; # See https://raymii.org/s/tutorials/HTTP_Strict_Transport_Security_for_Apache_NGINX_and_Lighttpd.html add_header Strict-Transport-Security max-age=63072000; add_header X-Frame-Options DENY; #---- See http://tautt.com/best-nginx-configuration-for-security/ ---- server_tokens off; include $ps_confdir/extra.conf; } _EOF_ sudo ln -fs $ps_confdir/nginx.conf /etc/nginx/sites-enabled/wildcardbug sudo service nginx stop || true sudo service nginx start sleep 2 sudo service nginx status case `sudo service nginx status` in *"nginx is running"*) echo "nginx started";; *) echo "FAIL: nginx failed to start"; exit 1;; esac } verify_https() { # Now set up a web server using this cert cat > extra.conf <<_EOF_ location /$path { # First attempt to serve request as file, then as directory try_files \$uri \$uri/; } _EOF_ nginx_create # Put a file there date > www/payload.txt # Verify that wget can retrieve it rm -f payload.txt wget \ --ca-certificate ca.crt \ https://$hostname/payload.txt if diff payload.txt www/payload.txt then echo PASS wget else echo FAIL wget exit 1 fi # Verify that curl can retrieve it rm -rf curl-payload.txt curl -v \ --cacert ca.crt \ https://$hostname/payload.txt > curl-payload.txt if diff curl-payload.txt www/payload.txt then echo PASS curl else echo FAIL curl exit 1 fi } create_dummy_deb() { local name=$1; shift if test -f ${name}_0.0.1_$dpkgarch.deb then return fi mkdir dummy.$$ echo 2.0 > dummy.$$/debian-binary mkdir -p dummy.$$/usr/local/bin cat > dummy.$$/usr/local/bin/$name <<_EOF_ #!/bin/sh echo 'hello, world!' _EOF_ chmod +x dummy.$$/usr/local/bin/$name # Work around bug in older fpm with funky option, if present? # See https://github.com/jordansissel/fpm/issues/923 fpm -s dir -t deb -n $name -v 0.0.1 -C dummy.$$ --deb-no-default-config-files usr || fpm -s dir -t deb -n $name -v 0.0.1 -C dummy.$$ usr rm -rf dummy.$$ } create_apt_tree() { # create a dummy package create_dummy_deb dummyhello # put it in a repo directory tree using reprepro rr_root=$ps_webroot/my.reprepro.dir rm -rf $rr_root mkdir $rr_root mkdir $rr_root/conf cat > $rr_root/conf/distributions <<_EOF_ Origin: SSL Test Label: SSL Test Repo Codename: $distro Architectures: $dpkgarch Components: main Description: repository containing dummy package, just to test 'apt install' from https apt repository _EOF_ reprepro --ask-passphrase -S main -Vb $rr_root includedeb $distro dummyhello*.deb } verify_https_apt() { # Create an apt server using this cert # See e.g. # http://davehall.com.au/blog/dave/2010/02/06/howto-setup-private-package-repository-reprepro-nginx # https://www.digitalocean.com/community/tutorials/how-to-use-reprepro-for-a-secure-package-repository-on-ubuntu-14-04 cat > extra.conf <<_EOF_ location /$path { # First attempt to serve request as file, then as directory try_files \$uri \$uri/; # Avoid redirecting things apt speculatively fetches but which don't exist location ~ /$path/*/(Translation-|Packages)* { # If this fires, the try_files above does not fire. } } _EOF_ nginx_create # Put a package there create_apt_tree # Configure apt client options to use when accessing this server REPONAME=$hostname # Create a client-side apt config file that turns on # Verify-Host (https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html ) # to make sure host isn't spoofed (this is why cert must match your real hostname), # (well, leave that off if your cert doesn't match), and turns on # Verify-Peer (https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html) # to make sure cert is signed by a trusted authority. # Also force TLS, since even SSLv3 is insecure. # And finally provide the CA bundle needed to trust the server. cat > 99$REPONAME << _EOF_ Acquire::https::$REPONAME::Verify-Peer "true"; Acquire::https::$REPONAME::Verify-Host "true"; Acquire::https::$REPONAME::SslForceVersion "TLSv1"; Acquire::https::$REPONAME::CaInfo "$ps_confdir/ca.crt"; _EOF_ # Remember to remove this after the test sudo install -D -m 644 99$REPONAME /etc/apt/apt.conf.d/99`echo $REPONAME | tr . _` # Now point to that server echo "deb [arch=$dpkgarch] https://$REPONAME/my.reprepro.dir $distro main" | sudo tee /etc/apt/sources.list.d/$REPONAME.list # Tell apt to look at the dummy repo sudo apt-get update # Install the dummy package from the dummy server if sudo apt-get install dummyhello then echo PASS apt-get install else echo FAIL apt-get install exit 1 fi # Remove it and the config files sudo dpkg -r dummyhello sudo rm /etc/apt/apt.conf.d/99`echo $REPONAME | tr . _` /etc/apt/sources.list.d/$REPONAME.list } generate_fake_wildcard_cert echo "Verify that fake cert works with wget and curl" verify_https echo "Verify that fake cert works with apt" verify_https_apt if test -f realcert/server.crt then cp realcert/* . echo "Verify that real cert works with wget and curl" verify_https echo "Verify that real cert works with apt" verify_https_apt else echo "Place real cert, key, and ca bundle in realcert/ and rerun to test with real cert" fi rm server.crt server.key ca.crt echo SUCCESS