Skip to content

Instantly share code, notes, and snippets.

@mfdj
Last active August 29, 2015 13:57
Show Gist options
  • Save mfdj/9461287 to your computer and use it in GitHub Desktop.
Save mfdj/9461287 to your computer and use it in GitHub Desktop.
Generate multiple clean, workable local "mini Certificate Authorities" preconfigured to be good enough. Based on OpenSSL. Developed for Bash on OSX (should be pretty portable).
#!/usr/bin/env bash
function defaultinput()
{
local LABEL=$1
local DEFAULT=$2
local __return=$3
echo -n "$LABEL [$DEFAULT] "
read input_a
if [[ -z "$input_a" ]]; then
eval $__return='$DEFAULT'
else
eval $__return='$input_a'
fi
}
# ~~~~~~~~~~~~~~~~~ inputs: name, full name
NAME=$1
if [ -z "$NAME" ]; then
echo -n 'Domain for your CA [myca.com]: '
read input_d
NAME=$input_d
if [ -z "$input_d" ]; then
NAME='myca.com'
fi
fi
shift
FULLNAME="$@" # remaining arugments become fullname
if [ -z "$FULLNAME" ]; then
echo -n "Displayname [$NAME] "
read input_d
FULLNAME=$input_d
if [ -z "$input_d" ]; then
FULLNAME=$NAME
fi
fi
# ~~~~~~~~~~~~~~~~~ create folder for ca, using $NAME
if [ -d "$NAME" ]; then
echo -n "${NAME} exists: Overwrite config files (y/n)? [n] "
read input_d
if [ "$input_d" == 'y' ]; then
echo "{ overwriting }"
else
exit
fi
else
mkdir "$NAME"
fi
# ~~~~~~~~~~~~~~~~~ folders and misc file stubs
cd $NAME
mkdir -p cadata/serialized certs config
touch "cadata/database" "makecert"
chmod +x makecert
echo 0001 > "cadata/database_serial"
# ~~~~~~~~~~~~~~~~~ request header, with distingushed name defaults
# customize to populate your root and subordinate cert requests
# with senisble defaults. These are just defaults for interactive prompts:
# all values can be overidden per certificate
echo "::: making certificate request template"
defaultinput "Country?" "US" COUNTRY
defaultinput "State?" "California" STATE
defaultinput "City?" "Los Angeles" CITY
cat >config/req.header <<EOL
[ req ]
distinguished_name = req_distinguished_name
x509_extensions = req_x509_extensions
[ req_distinguished_name ]
countryName = $COUNTRY
countryName_default = US
stateOrProvinceName = State
stateOrProvinceName_default = $STATE
localityName = City
localityName_default = $CITY
organizationName = Organization
organizationalUnitName = Unit
organizationName_default = $FULLNAME
EOL
# ~~~~~~~~~~~~~~~~~ config/req-root.conf
# req-root.conf is used to generate the root certificate.
# Configurations for using OCSP and CPS options are commented out
# but are stubbed into the file.
# * OCSP: Online Certificate Status Protocol
# * CPS: Certificate Policy Statement
cp config/req.header config/req-root.conf
cat >>config/req-root.conf <<EOL
organizationalUnitName_default = Internal Certificate Authority
commonName = Common Name
commonName_default = $FULLNAME x509/TLS Signatory
[ req_x509_extensions ]
basicConstraints = CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
# certificatePolicies = @subordinate_certificatePolicies
# authorityInfoAccess = OCSP;URI:http://$NAME/ocsp/
# [ subordinate_certificatePolicies ]
# CPS.1 = http://$NAME/cps/
# userNotice.1 = @subordinate_cp_notice
# [ subordinate_cp_notice ]
# organization = "$FULLNAME"
# explicitText = "More info at http://$NAME/ca/"
EOL
# ~~~~~~~~~~~~~~~~~ CA root.key + root.crt
# Create root key/cert. Won't overide previously created.
# Every cert issued by a CA needs the root.key and root.crt.
# If you want to stop creating certs delete the root key.
# If you want to revoke your root.crt check out OCSP.
# Your root.crt is the public key that clients should trust.
if [ ! -s "root.key" ]; then
# create root key
# - safely managing this key is a high priority
# - if you're key is compromised having a strong password can slow
# and maybe prevent certificate forgers (stronger the better)
openssl genrsa -des3 -out "root.key" 4096
# create root cert
# - valid for 2 years, a short period of time for a real CA but for
# small CAs YMMV
openssl req -new -x509 -days 730 \
-config "config/req-root.conf" \
-key "root.key" \
-out "root.crt"
fi
# ~~~~~~~~~~~~~~~~~ makecert script
# Simplifies the creation of issuing certificates per CA
cat >makecert <<"EOL2"
#!/usr/bin/env bash
WILDCARD=
OPTIND=1
while getopts "w" opt; do
case $opt in
w) WILDCARD=1;;
esac
done
shift "$((OPTIND-1))"
DOMAIN=$1
if [ -z "$DOMAIN" ]; then
echo -n 'Domain [hello.local]: '
read input_d
DOMAIN=$input_d
if [ -z "$input_d" ]; then
DOMAIN='hello.local'
fi
else
shift
fi
OPTIND=1
while getopts "w" opt; do
case $opt in
w) WILDCARD=1;;
esac
done
shift "$((OPTIND-1))"
cp config/req.header config/req-$DOMAIN.conf
cat >>config/req-$DOMAIN.conf <<EOL
commonName = Common Name
commonName_default = $DOMAIN
[ req_x509_extensions ]
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
EOL
CONF='config/ca.conf'
if [ "$WILDCARD" ]; then
CONF="config/ca-wildcard-$DOMAIN.conf"
cp config/ca.conf $CONF
cat >>$CONF <<EOL
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = $DOMAIN
DNS.2 = *.$DOMAIN
EOL
fi
if [ -s certs/$DOMAIN.key ]; then
echo -n "Key found. Overwrite key? [n] "
read input_yn
if [ "$input_yn" == 'y' -o "$input_yn" == 'Y' ]; then
rm certs/$DOMAIN.key
openssl genrsa -out certs/$DOMAIN.key 4096
fi
else
openssl genrsa -out certs/$DOMAIN.key 4096
fi
openssl req -new -config config/req-${DOMAIN}.conf -key certs/$DOMAIN.key -out $DOMAIN.csr
openssl ca -batch -notext -config $CONF -in $DOMAIN.csr -out certs/$DOMAIN.crt
rm *csr
EOL2
# ~~~~~~~~~~~~~~~~~ config/ca.conf
# stable boilerplate CA configuration file
# used for sigining certs
cat >"config/ca.conf" <<EOL
[ ca ]
default_ca = myca
[ myca ]
new_certs_dir = ./cadata/serialized
unique_subject = no
private_key = ./root.key
certificate = ./root.crt
database = ./cadata/database
serial = ./cadata/database_serial
default_days = 365
default_md = sha1
policy = subordinate_policy
x509_extensions = subordinate_extensions
[ subordinate_policy ]
commonName = supplied
stateOrProvinceName = supplied
countryName = supplied
emailAddress = optional
organizationName = supplied
organizationalUnitName = optional
[ subordinate_extensions ]
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
keyUsage = digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
EOL

usage

  1. create project folder (certauth)
  2. grab the script from github
  3. make it executable
  4. run it ./new_ca <domain.name/shortname> <Display Name>
  • be aware: you'll need a 5+ letter password to protect your root.key
    • note: you'll enter 3 times in row rapidly
  • you'll enter some info about your CA's root.crt
  1. change into your new CA folder
  2. run ./makecert <domain.name> [-w]
  • enter some info for the certificate you're signing
  1. Examine the cert
$ mkdir certauth && cd certauth
$ wget -O new_ca https://gist.githubusercontent.com/mfdj/9461287/raw/5951182f8ee0da9b20ba2baa531b17d40d2d2414/openssl_ca_recipe.sh
$ chmod +x new_ca
$ ./new_ca myca.local Cool Cool, Stuff CA
$ cd myca.local
$ ./makecert single.local
$ ./makecert wildcard.local -w
$ openssl x509 -text -noout -in certs/wildcard.local.crt

one-liner of above

mkdir certauth && cd $_ && \
wget -O new_ca https://gist.githubusercontent.com/mfdj/9461287/raw/5951182f8ee0da9b20ba2baa531b17d40d2d2414/openssl_ca_recipe.sh && \
chmod +x new_ca && \
./new_ca myca.local Cool Cool, Stuff CA && \
cd myca.local && \
./makecert wildcard.local -w && \
openssl x509 -text -noout -in certs/wildcard.local.crt

install your root.crt on OSX to make your certs trusted

On OSX you can make Chrome/Safari aware of youre CA which has the benifit of avoiding warnings about your untrusted certificates just run $ open root.crt or double-click the file to install your root certficate into Keychain. Once installed Chrome/Safari should trust any certificate signed by your CA using $ ./makecert.

Feedback welcome: @markfoxisadj

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment