How to generate a valid self signed ssl certificate for Vagrant Homestead Improved

Valid SSL Certificate on Chrome and Firefox

Hi there! Recently I started reading a book “PHP & MySQL Novice to Ninja” and it encouraged me to set up a VirtualBox and Vagrant with Homestead Improved image, which is a fork of Laravel Homestead. There are good articles on why and how you should do it.

However, as soon as I started this virtual machine and pointed my Google Chrome to it, it said, “Security Risk” and simply refused to open a page, and there wasn’t any “proceed anyway” or “add exception” options in advanced section.

Google search gave me some advice on how to fix it, but none of them actually worked. All I was able to get is to visit a site with a red warning in the browsers address bar and “Not Secure” message.

Valid SSL Certificate on Chrome and Firefox
Valid SSL Certificate on Chrome and Firefox

It took me whole two days of experimentation to make address bar turn green on Chrome and Firefox.

The Cause of Invalid Certificate

First Cause is that certificates meant to be signed by Trusted Certification Authority. That way Self-Signed certificates can’t be trusted because they don’t have valid (trusted) signature.

Second Cause is that Google Chrome expects certificate to have a Subject Alternative Name (SAN) extension. Without it address bar stays red.

Partial Solution

You can generate self-signed certificate with SAN extension, and import it to Trusted Root Certification Authorities section of your certificate manager. This article was helpful to me at that solution stage.

This solution turned Chrome’s address bar green for me. However, certificate manager on Firefox refuses to import certificate meant to validate a website to a Trusted Root Certification Authorities section meant for Certification Authorities. Firefox allows you to add exception and you can visit a site, but address bar stays yellow and says, “Not Secure”.

If you choose this route you will have to import a certificate every time for every new site created on your virtual machine.

Working Solution

The solution that worked for me was to create a self-signed Certification Authority (CA) certificate first and only then generate certificates (with SAN extension) for virtual sites signed by this self-signed CA.

Now if you import that self-signed CA certificate to Trusted Root Certification Authorities section of your certificate manager (and Firefox accepts it) it will make every site certificate that was signed by it valid and trusted.

This way you will import certificate only once and it will make every site on your virtual machine work.

How to do it

I will create two websites on my Homestead Improved virtual machine “sandbox.dev” and “gravelbox.test” to demonstrate how it works step by step.

Edit Windows hosts file

Firstly let’s add those two addresses to the windows “hosts” file. To do that open your text editor as Administrator and open file:

C:\Windows\System32\drivers\etc\hosts

Now add these lines to it:

  
  
192.168.10.10 sandbox.dev
192.168.10.10 www.sandbox.dev

192.168.10.10 gravelbox.test
192.168.10.10 www.gravelbox.test
  

You may skip the “www.” lines if you don’t need them. However, I will make them work in my example.

Edit Homestead.yaml file and add index pages

Add these lines to your “Homestead.yaml” file “sites” section (please don’t repeat the line “sites:”). This file is located in the folder you’ve cloned Homestead Improved Git repository.

  
  
sites:
    - map: sandbox.dev
      to: /home/vagrant/Code/sandbox/public
  
    - map: gravelbox.test
      to: /home/vagrant/Code/gravelbox/public
  

Make sure the paths are correct on your installation and create project folders “sandbox/public” and “gravelbox/public”.

Add “index.php” files to those folders.

  
  
<!DOCTYPE html>
<html>
  <body>
    <h1 style="width: 400px; margin: 150px auto 0 auto;">
      Hello Sandbox/Gravelbox!
    </h1>
  </body>
</html>
  

Delete existing certificates

Login to your virtual server (run command “vagrant ssh”) and navigate to folder: “/etc/nginx/ssl/” (run command “cd /etc/nginx/ssl”).

List files there (run command “ls -l”) and delete all files that start with “sandbox.dev.” and “gravelbox.test.”. By default three files are generated if you’ve already tried to set up a site. These files have extensions “.crt”, “.csr” and “.key”. The commands to delete them would be:

  
  
rm -f sandbox.dev.*
rm -f gravelbox.test.*
  

Edit BASH script that generates certificates

Navigate to the same folder where you have the “Homestead.yaml” file (from Windows, not from the terminal, although should work both ways).

Inside folder “scripts” find file “serve-laravel.sh” and make a backup copy of it. This script runs every time you run provision. It runs for every site.

Now open the original file with your favorite code editor. I will list the changes I’ve made to the script below. You can download the file to make it easier to copy-paste or you can just overwrite the file.

Generating Root CA certificate

First we’ll generate the self-signed Root Certification Authority certificate. Before that we are checking if it exists, and running a command if it’s not. The output is two files. The private key and the self-signed certificate.

  
  
PATH_CA_ROOT_CRT="${PATH_SSL}/homestead_root_ca.crt"
PATH_CA_ROOT_KEY="${PATH_SSL}/homestead_root_ca.key"

if [ ! -f $PATH_CA_ROOT_CRT ] || [ ! -f $PATH_CA_ROOT_KEY ]
then
  openssl req -x509 -nodes -newkey rsa:4096 -keyout "$PATH_CA_ROOT_KEY" -out "$PATH_CA_ROOT_CRT" -days 365 -subj "/C=UK/O=Vagrant Homestead Improved/CN=Vagrant Homestead Improved Root CA" 2>/dev/null
fi
  

Generating file with SAN extension parameters

We need to add Subject Alternative Name (SAN) extension to the site certificates. To do that we are creating a file with required options. We will use this file in next step. Note that this adds support for “www.” sub-domain.

  
  
PATH_SAN_EXTENSION="${PATH_SSL}/${1}.san"
  
if [ ! -f $PATH_SAN_EXTENSION ]
then
  printf "[SAN]\nsubjectAltName=DNS:${1},DNS:www.${1}\n" > $PATH_SAN_EXTENSION
fi
  

Generating site certificates

Next we will generate site certificate with SAN extension and sign it by our Root CA.

First “openssl” line is unchanged, it generates a private key. Second command generates a certificate signing request (CSR), I’ve simplified the subject here, but you may leave this line as it is. Third line signs the CSR, adds SAN to it and creates site certificate file. The “.srl” file is needed to keep track of serial numbers for certificates signed by Root CA.

  
  
PATH_CA_ROOT_SRL="${PATH_SSL}/homestead_root_ca.srl"
  
if [ ! -f $PATH_KEY ] || [ ! -f $PATH_CSR ] || [ ! -f $PATH_CRT ]
  openssl genrsa -out "$PATH_KEY" 2048 2>/dev/null
  openssl req -new -key "$PATH_KEY" -out "$PATH_CSR" -subj "/CN=$1" 2>/dev/null
  openssl x509 -req -days 365 -extfile "$PATH_SAN_EXTENSION" -extensions "SAN" -CAcreateserial -CAserial "$PATH_CA_ROOT_SRL" -CAkey "$PATH_CA_ROOT_KEY" -CA "$PATH_CA_ROOT_CRT" -in "$PATH_CSR" -out "$PATH_CRT" 2>/dev/null
fi
  

Adding server name alias

Here we’ll add alias for “www.” sub-domain. You may skip this step.

Find the line ‘block=”server {‘ in the script. See below.

  
  
block="server {
    listen ${3:-80};
    listen ${4:-443} ssl http2;
    server_name $1;
    root \"$2\";
  

Change the fourth line from “block=” that says “server_name” like shown below.

  
  
server_name $1 www.$1;
  

The final script

The whole file (download) looks like this:

#!/usr/bin/env bash

mkdir /etc/nginx/ssl 2>/dev/null

PATH_SSL="/etc/nginx/ssl"
PATH_KEY="${PATH_SSL}/${1}.key"
PATH_CSR="${PATH_SSL}/${1}.csr"
PATH_CRT="${PATH_SSL}/${1}.crt"

PATH_SAN_EXTENSION="${PATH_SSL}/${1}.san"

PATH_CA_ROOT_CRT="${PATH_SSL}/homestead_root_ca.crt"
PATH_CA_ROOT_KEY="${PATH_SSL}/homestead_root_ca.key"
PATH_CA_ROOT_SRL="${PATH_SSL}/homestead_root_ca.srl"

if [ ! -f $PATH_CA_ROOT_CRT ] || [ ! -f $PATH_CA_ROOT_KEY ]
then
  openssl req -x509 -nodes -newkey rsa:4096 -keyout "$PATH_CA_ROOT_KEY" -out "$PATH_CA_ROOT_CRT" -days 365 -subj "/C=UK/O=Vagrant Homestead Improved/CN=Vagrant Homestead Improved Root CA" 2>/dev/null
fi

if [ ! -f $PATH_SAN_EXTENSION ]
then
  printf "[SAN]\nsubjectAltName=DNS:${1},DNS:www.${1}\n" > $PATH_SAN_EXTENSION
fi

if [ ! -f $PATH_KEY ] || [ ! -f $PATH_CSR ] || [ ! -f $PATH_CRT ]
then
  openssl genrsa -out "$PATH_KEY" 2048 2>/dev/null
  openssl req -new -key "$PATH_KEY" -out "$PATH_CSR" -subj "/CN=$1" -days 365 2>/dev/null
  openssl x509 -req -extfile "$PATH_SAN_EXTENSION" -extensions "SAN" -CAcreateserial -CAserial "$PATH_CA_ROOT_SRL" -CAkey "$PATH_CA_ROOT_KEY" -CA "$PATH_CA_ROOT_CRT" -in "$PATH_CSR" -out "$PATH_CRT" 2>/dev/null
fi


block="server {
    listen ${3:-80};
    listen ${4:-443} ssl http2;
    server_name $1 www.$1;
    root \"$2\";

    index index.html index.htm index.php;

    charset utf-8;

    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt { access_log off; log_not_found off; }

    access_log off;
    error_log /var/log/nginx/$1-error.log error;

    sendfile off;

    client_max_body_size 100m;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;

        fastcgi_intercept_errors off;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
    }

    location ~ /\.ht {
        deny all;
    }

    ssl_certificate /etc/nginx/ssl/$1.crt;
    ssl_certificate_key /etc/nginx/ssl/$1.key;
}
"

echo "$block" > "/etc/nginx/sites-available/$1"
ln -fs "/etc/nginx/sites-available/$1" "/etc/nginx/sites-enabled/$1"
  

Run provision

Now run provision on a running virtual machine (run command “vagrant provision”). This should create all certificates and set up sites.

Copy and Import Root CA Certificate

Login to your virtual server (run command “vagrant ssh”). Copy Root CA certificate to your project folder so you can reach it on Windows. In my case folder may be “/home/vagrant/Code/sandbox/”. Command follows:

  
  
cp /etc/nginx/ssl/homestead_root_ca.crt /home/vagrant/Code/sandbox/
  

Open certificates manager. In Chrome, go to Settings > Advanced > Manage certificates. Click on “Trusted Root Certification Authorities” tab. Click import and browse to the copied Root CA certificate. Make sure to import to “Trusted Root Certification Authorities” section.

In Firefox, go to Options > Privacy & Security > Certificates (Section at very bottom) > View Certificates. Click on “Authorities” tab. Click import and browse to the copied Root CA certificate. Check “Trust this CA to identify websites”.

Finally

Try opening sites “https://sandbox.dev” and “https://gravelbox.test” in your browser. Address bar should be green. You may examine certificates if you are curious.

Hope this worked for you.

Troubleshooting

If the BASH script didn’t work you may try to remove the ending “2>/dev/null” on every “openssl” line as it suppresses all output including errors.

Next, delete certificates and related files (including root ca) in “/etc/nginx/ssl” folder.

Next, try running the script from command line as root (to become root type “sudo su”). Navigate to “scripts” folder (check your path):

  
  
cd /home/vagrant/Code/scripts/
  

Now run the script (first argument is your site name, second the working folder for that site):

  
  
./serve-laravel.sh "sandbox.dev" "/home/vagrant/Code/sandbox/public"
  

And look for errors. Before running the script again or before provisioning, you should delete the certificate related files again. And obviously if you’ve deleted the Root certificate you should import it again after it gets created.

Good luck.

One thought on “How to generate a valid self signed ssl certificate for Vagrant Homestead Improved”

Leave a Reply

Your email address will not be published. Required fields are marked *