Loading .bin/mk-cert 0 → 100755 +198 −0 Original line number Diff line number Diff line #!/usr/bin/env bash usage() { tee <<END_HELP $(basename $0) [-h] [-o OUTPUT] NAME [NAME ...] Generate local development certificates, signed by a local CA Options: -h --help Show this message and exit -o --output OUTPUT Output file name (default: stdout) Arguments: NAME A domain name that the generated certificate will validate END_HELP } badarg() { usage tput setaf 1 echo "Bad arguments: $*" tput sgr0 exit 2 } >&2 set -eu -o pipefail shopt -s extglob failglob globstar nullglob shopt -s inherit_errexit lastpipe source ~/.shell/lib/builtins.bash declare -r CONFIG_SERIAL=1 declare -r KEYNAME=secp384r1 declare -r CACHE=${XDG_CACHE_HOME:=$HOME/.cache}/dev-certs declare -r CONFIG=$CACHE/config-$CONFIG_SERIAL.ini declare -rx CA_DIR=$CACHE/ca declare -rx CERTS_DIR=$CACHE/certs declare -r OPENSSL=$(type -P openssl) if [[ -z $OPENSSL || $($OPENSSL version) =~ LibreSSL ]]; then echo >&2 "Please install OpenSSL (not LibreSSL)" exit 3 fi declare -a ARGS_NAMES=() declare ARG_OUTPUT until [[ $# -eq 0 ]]; do case $1 in -h|--help) usage; exit ;; -o|--output) ARG_OUTPUT=$2; shift ;; --) ARGS_NAMES+=( "$@" ); break ;; -*) badarg "Unknown option: $1" ;; *) ARGS_NAMES+=( $1 ) ;; esac shift done [[ ${#ARGS_NAMES[*]} -gt 0 ]] || badarg "Need at least one domain name for the certificate" openssl() { $OPENSSL "$@"; } join() { local IFS=$1 echo "${*:2}" } get_pass() { while [[ ! -v CA_PASS ]]; do read -sp 'CA Key password: ' CA_PASS; echo >&2 [[ ${1-nocheck} != check || -z $CA_PASS ]] && return read -sp 'Confirm password: ' check; echo >&2 if [[ $check != $CA_PASS ]]; then unset CA_PASS echo >&2 "Passwords did not match, try again" fi done } check_for_current() { local now=$(date +%y%m%d%H%M%S) local exp serial subject _1 _2 while read _1 exp serial _2 subject; do [[ $subject = $1 ]] || continue if [[ $now -lt ${exp%Z} ]]; then echo >&2 "Certificate already exists!" echo $serial return fi done <$CA_DIR/index } # generate a local CA cert if not already present generate_ca() { mkdir -p "$CA_DIR" "$CERTS_DIR" [[ -e $CA_DIR/index ]] || touch "$CA_DIR/index" [[ -e $CA_DIR/serial ]] || echo "01" >$CA_DIR/serial [[ -e $CA_DIR/ca.crt ]] && return get_pass check openssl ecparam -genkey -name $KEYNAME | openssl ec -aes256 -passout fd:3 3<<<"$CA_PASS" | tee "$CA_DIR/ca.key" | openssl req -x509 -new -batch \ -config "$CONFIG" \ -key /dev/stdin \ -passin fd:3 3<<<"$CA_PASS" \ -out "$CA_DIR/ca.crt" \ -days $((365*10)) \ -subj '/O=Local Development/CN=Local Development CA' } # generate a local certificate for lists of subjects generate_cert() ( [[ $# -gt 0 ]] || return (IFS=:; sort -u <<<"$*") | md5sum | read ID _ export ID [[ -e $CACHE/$ID.pem ]] && return local alt_subjects=$(join , "${@/#/DNS:}") local subject="/O=Local Development/CN=Local Dev Certificate ($ID)" local serial=$(check_for_current "$subject") if [[ -z $serial ]]; then serial=$(cat "$CA_DIR/serial") mkdir -p "$CERTS_DIR/$ID" get_pass openssl ecparam -genkey \ -name $KEYNAME \ -outform pem | tee "$CERTS_DIR/$ID/$serial.key" | openssl req -new -batch \ -config "$CONFIG" \ -addext "subjectAltName = $alt_subjects" \ -key /dev/stdin \ -subj "$subject" | openssl ca -batch \ -config "$CONFIG" \ -days 365 \ -passin fd:3 3<<<"$CA_PASS" \ -in /dev/stdin fi >/dev/null cat >${ARG_OUTPUT-/dev/stdout} \ "$CERTS_DIR/$ID/$serial.pem" \ "$CERTS_DIR/$ID/$serial.key" \ "$CA_DIR/ca.crt" ) generate_config() { [[ -e $CONFIG ]] && return mkdir -p $(dirname "$CONFIG") tee >$CONFIG <<-'END_INI' # default CA directory CA_DIR = ${ENV::HOME}/.cache/dev-certs/ca ID=null [ req ] prompt = no distinguished_name = distinguished_name [ ca ] default_ca = ca_default x509_extensions = ca_extensions [ ca_extensions ] basicConstraints = critical, CA:FALSE [ ca_default ] dir = ${ENV::CA_DIR} database = $dir/index new_certs_dir = $dir/../certs/${ENV::ID} certificate = $dir/ca.crt serial = $dir/serial private_key = $dir/ca.key policy = ca_policy copy_extensions = copy unique_subject = no default_md = sha512 [ ca_policy ] organizationName = match commonName = supplied [ distinguished_name ] # O = Local Development END_INI } generate_config generate_ca generate_cert "${ARGS_NAMES[@]}" .shell/lib/builtins.bash 0 → 100644 +23 −0 Original line number Diff line number Diff line test -n "$BASH" || return load() { local src=${BASH%/bin/bash}/lib/bash/$1 test -r "$src" || return 0 enable -f "$src" ${2-$1} } load basename load dirname load head load id load ln load mkdir load realpath load rmdir load seq load sleep load tee load uname load unlink unset -f load Loading
.bin/mk-cert 0 → 100755 +198 −0 Original line number Diff line number Diff line #!/usr/bin/env bash usage() { tee <<END_HELP $(basename $0) [-h] [-o OUTPUT] NAME [NAME ...] Generate local development certificates, signed by a local CA Options: -h --help Show this message and exit -o --output OUTPUT Output file name (default: stdout) Arguments: NAME A domain name that the generated certificate will validate END_HELP } badarg() { usage tput setaf 1 echo "Bad arguments: $*" tput sgr0 exit 2 } >&2 set -eu -o pipefail shopt -s extglob failglob globstar nullglob shopt -s inherit_errexit lastpipe source ~/.shell/lib/builtins.bash declare -r CONFIG_SERIAL=1 declare -r KEYNAME=secp384r1 declare -r CACHE=${XDG_CACHE_HOME:=$HOME/.cache}/dev-certs declare -r CONFIG=$CACHE/config-$CONFIG_SERIAL.ini declare -rx CA_DIR=$CACHE/ca declare -rx CERTS_DIR=$CACHE/certs declare -r OPENSSL=$(type -P openssl) if [[ -z $OPENSSL || $($OPENSSL version) =~ LibreSSL ]]; then echo >&2 "Please install OpenSSL (not LibreSSL)" exit 3 fi declare -a ARGS_NAMES=() declare ARG_OUTPUT until [[ $# -eq 0 ]]; do case $1 in -h|--help) usage; exit ;; -o|--output) ARG_OUTPUT=$2; shift ;; --) ARGS_NAMES+=( "$@" ); break ;; -*) badarg "Unknown option: $1" ;; *) ARGS_NAMES+=( $1 ) ;; esac shift done [[ ${#ARGS_NAMES[*]} -gt 0 ]] || badarg "Need at least one domain name for the certificate" openssl() { $OPENSSL "$@"; } join() { local IFS=$1 echo "${*:2}" } get_pass() { while [[ ! -v CA_PASS ]]; do read -sp 'CA Key password: ' CA_PASS; echo >&2 [[ ${1-nocheck} != check || -z $CA_PASS ]] && return read -sp 'Confirm password: ' check; echo >&2 if [[ $check != $CA_PASS ]]; then unset CA_PASS echo >&2 "Passwords did not match, try again" fi done } check_for_current() { local now=$(date +%y%m%d%H%M%S) local exp serial subject _1 _2 while read _1 exp serial _2 subject; do [[ $subject = $1 ]] || continue if [[ $now -lt ${exp%Z} ]]; then echo >&2 "Certificate already exists!" echo $serial return fi done <$CA_DIR/index } # generate a local CA cert if not already present generate_ca() { mkdir -p "$CA_DIR" "$CERTS_DIR" [[ -e $CA_DIR/index ]] || touch "$CA_DIR/index" [[ -e $CA_DIR/serial ]] || echo "01" >$CA_DIR/serial [[ -e $CA_DIR/ca.crt ]] && return get_pass check openssl ecparam -genkey -name $KEYNAME | openssl ec -aes256 -passout fd:3 3<<<"$CA_PASS" | tee "$CA_DIR/ca.key" | openssl req -x509 -new -batch \ -config "$CONFIG" \ -key /dev/stdin \ -passin fd:3 3<<<"$CA_PASS" \ -out "$CA_DIR/ca.crt" \ -days $((365*10)) \ -subj '/O=Local Development/CN=Local Development CA' } # generate a local certificate for lists of subjects generate_cert() ( [[ $# -gt 0 ]] || return (IFS=:; sort -u <<<"$*") | md5sum | read ID _ export ID [[ -e $CACHE/$ID.pem ]] && return local alt_subjects=$(join , "${@/#/DNS:}") local subject="/O=Local Development/CN=Local Dev Certificate ($ID)" local serial=$(check_for_current "$subject") if [[ -z $serial ]]; then serial=$(cat "$CA_DIR/serial") mkdir -p "$CERTS_DIR/$ID" get_pass openssl ecparam -genkey \ -name $KEYNAME \ -outform pem | tee "$CERTS_DIR/$ID/$serial.key" | openssl req -new -batch \ -config "$CONFIG" \ -addext "subjectAltName = $alt_subjects" \ -key /dev/stdin \ -subj "$subject" | openssl ca -batch \ -config "$CONFIG" \ -days 365 \ -passin fd:3 3<<<"$CA_PASS" \ -in /dev/stdin fi >/dev/null cat >${ARG_OUTPUT-/dev/stdout} \ "$CERTS_DIR/$ID/$serial.pem" \ "$CERTS_DIR/$ID/$serial.key" \ "$CA_DIR/ca.crt" ) generate_config() { [[ -e $CONFIG ]] && return mkdir -p $(dirname "$CONFIG") tee >$CONFIG <<-'END_INI' # default CA directory CA_DIR = ${ENV::HOME}/.cache/dev-certs/ca ID=null [ req ] prompt = no distinguished_name = distinguished_name [ ca ] default_ca = ca_default x509_extensions = ca_extensions [ ca_extensions ] basicConstraints = critical, CA:FALSE [ ca_default ] dir = ${ENV::CA_DIR} database = $dir/index new_certs_dir = $dir/../certs/${ENV::ID} certificate = $dir/ca.crt serial = $dir/serial private_key = $dir/ca.key policy = ca_policy copy_extensions = copy unique_subject = no default_md = sha512 [ ca_policy ] organizationName = match commonName = supplied [ distinguished_name ] # O = Local Development END_INI } generate_config generate_ca generate_cert "${ARGS_NAMES[@]}"
.shell/lib/builtins.bash 0 → 100644 +23 −0 Original line number Diff line number Diff line test -n "$BASH" || return load() { local src=${BASH%/bin/bash}/lib/bash/$1 test -r "$src" || return 0 enable -f "$src" ${2-$1} } load basename load dirname load head load id load ln load mkdir load realpath load rmdir load seq load sleep load tee load uname load unlink unset -f load