#!/usr/bin/env bash # $Id: mkpem,v 1.21 2019/01/10 20:03:35 friedman Exp $ # Commentary: # Example config.dn for an SSL server certificate: # # C = US # ST = California # L = San Francisco # O = Nocturnal Aviation, Inc. # OU = Shipping and Receiving # CN = foo.bar.com # # [ v3_ca ] # subjectAltName = @alt_names # # [ alt_names ] # DNS.01 = cname.bar.com # DNS.02 = othercname.bar.com # IP.01 = 127.0.0.1 # IP.02 = ::1 # email.01 = foo@bar.com # # Order is least significant to most significant! # Usually you want to list C first and CN last. # # The v3_ca and alt_names sections are optional. # To import a web cert into a NSS database: # # certutil -d [sql|dbm]:[/dir] -A -n [nickname] -t P -i [file.crt] # # Use -t CT,C,C for CA certificates. # Code: apush() { eval "$1=(\"\${$1[@]}\" \"\${@:2}\")"; } confparam() { declare s='[ ]*' # SPC TAB declare -a expr case ${2+isset} in isset ) apush expr -e "1,/^$s\[$s$2$s\]/d" apush expr -e "/^$s\[.*\]/q" ;; esac apush expr -e "/^$s$1$s=$s\(.*\)/{s//\1/;s/$s\$//;p;q;}" apush expr -e "/^$s\($1\)$s\$/{s//\1/p;q;}" sed -n "${expr[@]}" "$config" } # For any options not given explicitly, see if they are set in the config. # If there isn't one there either, fill in the missing option from defaults. fill_options_from_config() { declare param declare val for param in "${!default[@]}"; do case ${opt[$param]+isset} in isset ) continue ;; esac opt[$param]=${default[$param]} case $param in task ) for param in csr pem ; do val=$(confparam --$param mkpem_options) case $val in --$param ) opt[task]=$param; break ;; esac done continue ;; esac val=$(confparam --$param mkpem_options) case $val in --$param ) opt[$param]=t ; continue ;; esac val=$(confparam --no-$param mkpem_options) case $val in --no-$param ) opt[$param]=f ; continue ;; esac done #declare -p opt; exit # debugging } # Return the next emacs-style backup file name for a given file on disk, # based on the VERSION_CONTROL environment variable. # `t' or `numbered' means make numeric backup versions unconditionally. # `nil' or `existing' means make them for files that have some already. # `never' or `simple' means do not make them. make_backup_file_name() { declare name=$1 shift case $VERSION_CONTROL in never | simple ) result=$name~ ;; * ) highest=$(for f in "$name".~*~ ; do echo "$f"; done \ | sed -ne 's/~$//' -e 's/.*\.~//' -e p \ | sort -nr \ | head -1) case $highest in '*' | '' ) highest=0 ;; esac case $VERSION_CONTROL in nil | existing ) case $highest in 0 ) result=$name~ ;; esac ;; t | numbered | * ) next=$(( $highest + 1 )) result=$name.~$next~ ;; esac ;; esac echo "$result" } backup_files() { for f in "$@"; do if [ -f "$f" ]; then bck=$(make_backup_file_name "$f") echo "Moving existing $f -> $bck" mv "$f" "$bck" fi done } backup_and_set_keyopts() { declare key=$1; shift if [ -f "$key" ] && [ ".$opt_newkey" != .t ]; then backup_files "$@" keyopts=( -key "$key" ) else declare keyarg=rsa case $openssl_version in 0.9.8* ) # openssl 0.9.8 requires keysize on cmd line. declare sz=$(confparam default_bits) case $sz in '' ) sz=${MKPEM_KEYSIZE:-$4096} ;; esac keyarg=rsa:$sz ;; esac backup_files "$key" "$@" keyopts=( -newkey "$keyarg" -keyout "$key" -nodes ) fi } printconf() { echo "[ req ]" echo RANDFILE = /dev/null echo prompt = no if [ ${opt[extensions]} != f ]; then echo x509_extensions = v3_ca echo req_extensions = v3_ca fi echo distinguished_name = req_dn echo default_bits = ${MKPEM_KEYSIZE:-4096} echo default_md = sha256 echo utf8 = yes echo string_mask = utf8only echo echo "[ v3_ca ]" echo subjectKeyIdentifier = hash # Critical means cert should be rejected when used for purposes other # than those indicated in this extension. # # Settings for CA cert #echo basicConstraints = critical, CA:true, pathlen:0 #echo keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly #echo extendedKeyUsage = critical, serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, msSGC, nsSGC # Settings for basic self-signed web server cert echo basicConstraints = CA:false echo keyUsage = digitalSignature, keyEncipherment, keyCertSign echo extendedKeyUsage = serverAuth # Don't use nsCertType; deprecated. #echo nsCertType = critical, sslCA, emailCA, client, server, email, objsign #echo nsCertType = critical, server echo echo "[ req_dn ]" while read l; do # Do not include any [mkpem_options] section because later versions # of openssl do not allow non-assignment lines. case $l in *\[*mkpem_options*\]* ) while read l; do case $l in *\[*\]* ) break ;; esac; done ;; esac echo "$l" done < "$1" } vopenssl() { (set -x; openssl "$@"); } mkpem() { declare config=$1 declare base=$2 shift 2 declare pem=$base.pem declare crt=$base.crt declare key=$base.key declare yrs=${MKPEM_YEARS:-10} declare sn=${MKPEM_SERIAL:-$(TZ=UTC date +"$datefmt")} declare days=$(( 365 * $yrs )) backup_and_set_keyopts "$key" "$crt" "$pem" { printconf "$config" # This cannot be specified for CSRs, so add it here: echo "[ v3_ca ]" echo "authorityKeyIdentifier = keyid:always, issuer:always" } | $openssl req \ -config /dev/stdin \ -batch \ -x509 \ "${keyopts[@]}" \ -set_serial $sn \ -days $days \ -out "$crt" \ "$@" if [ -f "$crt" ]; then if [ ${opt[desc]} != f ]; then { echo $openssl x509 -in "$crt" -noout -text \ -nameopt RFC2253 \ -certopt ext_parse } >> "$crt" fi cat "$key" "$crt" > "$pem" else return 1 fi } mkcsr() { declare config=$1 declare base=$2 shift 2 declare asn1kludge=-asn1-kludge case ${opt[asn1-kludge]} in f ) asn1kludge=-no-asn1-kludge ;; esac case $openssl_version in 0.9.* | 1.0.* ) : ;; * ) asn1kludge= ;; # option dropped in 1.1.0 esac backup_and_set_keyopts "$base.key" "$base.csr" printconf "$config" \ | $openssl req \ -config /dev/stdin \ -batch \ "${keyopts[@]}" \ $asn1kludge \ -new \ -out "$base.csr" \ "$@" if [ -f "$base.csr" ]; then if [ ${opt[desc]} != f ]; then { echo $openssl req -in "$base.csr" -noout -text -verify \ -nameopt RFC2253 \ -reqopt ext_parse } >> "$base.csr" fi else return 1 fi } main() { umask 077 declare -A default=([extensions]=t [asn1-kludge]=f [newkey]=f [desc]=t [task]=pem) declare -A opt while : ; do case $# in 0) break ;; esac # fully qualify abbreviated options declare arg=$1 case $arg in -- ) shift; break ;; # Stop option processing --c* ) arg=--csr ;; --p* ) arg=--pem ;; --e* ) arg=--extensions ;; --no-e* ) arg=--no-extensions ;; --a* ) arg=--asn1-kludge ;; --no-a* ) arg=--no-asn1-kludge ;; --ne* ) arg=--newkey ;; --no-n* ) arg=--no-newkey ;; --d* ) arg=--desc ;; --no-d* ) arg=--no-desc ;; -* ) echo "$progname: unknown or ambiguous option \`$1'" 1>&2 exit 1 ;; * ) break ;; esac case $arg in --csr ) opt[task]=csr ;; --pem ) opt[task]=pem ;; * ) declare field=${1#--} declare bool=t case $field in no-* ) bool=f ; field=${field#no-} ;; esac case ${default[$field]+defined} in defined ) opt[$field]=$bool ;; * ) echo "$progname: unknown option \`$1'" 1>&2 exit 1 ;; esac ;; esac shift done case $# in 0 ) echo $"Usage: $(basename $0) configfile.dn" 1>&2 exit 1 ;; esac config=$1 shift fill_options_from_config datefmt='%Y%m%d%H%M%S' if date --help 2>&1 | grep nanoseconds > /dev/null ; then datefmt=$datefmt'%N' fi openssl=${MKPEM_OPENSSL:-openssl} openssl_version=$($openssl version | cut -d' ' -f2) basename=$(basename "$config" .dn) mk${opt[task]} "$config" "$basename" "$@" } main "$@" # eof