From 88f0d87066880385f5fe9173f98b65b55818525b Mon Sep 17 00:00:00 2001 From: Jonas Smedegaard Date: Wed, 19 Oct 2005 19:07:19 +0000 Subject: Major rewrite to support non-local CA cert handling. --- localmksslcerts | 394 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 233 insertions(+), 161 deletions(-) (limited to 'localmksslcerts') diff --git a/localmksslcerts b/localmksslcerts index bf65432..742a04a 100755 --- a/localmksslcerts +++ b/localmksslcerts @@ -1,85 +1,60 @@ #!/bin/sh # # /usr/local/sbin/localmksslcerts -# Copyright 2001-2004 Jonas Smedegaard +# Copyright 2001-2005 Jonas Smedegaard # -# $Id: localmksslcerts,v 1.20 2005-10-18 12:32:02 jonas Exp $ +# $Id: localmksslcerts,v 1.21 2005-10-19 19:07:19 jonas Exp $ # # Generate certificates for mail (and other) servers # Based on uw-imapd-ssl post-install script # -# TODO: Use getopts # TODO: Add symlink from CA certificate to cacert.pem if non-existing -# TODO: Default CA certificate is cacert.pem if --cacert not set set -e prg=$(basename $0) -copyright="(C) 2001-2004 Jonas Smedegaard " +copyright="(C) 2001-2005 Jonas Smedegaard " # Set some defaults PATH="$PATH:/usr/bin/ssl" DAYS2EXPIRE="365" SSLCERTDIR="/etc/ssl/certs" SSLKEYDIR="/etc/ssl/private" +HOSTNAME="$(hostname -f)" +DOMAINNAME="$(hostname -d)" -usage() { -echo "$prg, $copyright - -Usage: $prg [--fqdn ] [...] --daemon [...] [--force] - or: $prg [...] [-f] - -Options: - --fqdn Fully Qualified Domain Name for this host. - --cn Country Name (2 letter code) - --state State or Province Name (full name) - --loc Locality Name (eg, city) - --org Organisation/company - --ou Organisational unit/department - --daemon Daemon(s) in need for a certificate - (certificate is generated for each daemon) - --issuer Email address of entity issuing certificate - --cert Use certified host certificate - --cacert Where to store host certificate if missing - --makeca Create CA certificate if missing - -f, --force Force overwriting existing certificate(s) - -h, --help This help text - -Examples: - - Create certs for UW imapd/popd, bound to default host cert if there: - localmksslcerts imapd pop3d - - Create host cert for non-default hostname, overwriting stray files: - localmksslcerts --fqdn mail.jones.dk --cert --force - - Create host cert for non-default hostname and bind to UW daemons: - localmksslcerts --fqdn mail.jones.dk --cert --force imapd pop3d - - Create CA-signed host cert (and CA cert if not there): - localmksslcerts --cert --cacert IT-guide_dr_Jones_CA +CN="." +STATE="." +LOC="." +ORG="@@HOSTNAME@@" +OU="@@HOSTNAME@@" +FQDN="@@HOSTNAME@@" +ISSUER="postmaster@@@DOMAINNAME@@" +if [ -f /etc/local/mksslcerts.conf ]; then + . /etc/local/mksslcerts.conf +fi -If issuer is not given, \"postmaster@\" is used." -exit 1 -} +for var in CN STATE LOC ORG OU FQDN ISSUER; do + eval $var=\"\$\(echo \"\$$var\" \| /bin/sed -e \"s/@@HOSTNAME@@/$HOSTNAME/g\" -e \"s/@@DOMAINNAME@@/$DOMAINNAME/g\"\)\"; +done mkcerthash() { - filebase="$1" - filename="$filebase.pem" - certhash="$(openssl x509 -noout -hash -in "$SSLCERTDIR/$filename")" - hashfile="$certhash.0" - ln -sf "$filename" "$SSLCERTDIR/$hashfile" + base="$1" + certfile="$base.pem" + certhash="$(openssl x509 -noout -hash -in "$SSLCERTDIR/$certfile")" + ln -sf "$certfile" "$SSLCERTDIR/$certhash.0" } mkselfcert() { - filebase="$1" + base="$1" domain="$2" - filename="$filebase.pem" + keypath="$SSLKEYDIR/$base.pem" + certpath="$SSLCERTDIR/$base.pem" openssl req -new -x509 -nodes \ -days "$DAYS2EXPIRE" \ - -keyout "$SSLCERTDIR/$filename" \ - -out "$SSLCERTDIR/$filename" > /dev/null 2>&1 <<+ + -keyout "$keypath" \ + -out "$certpath" > /dev/null 2>&1 <<+ $cn $state $loc @@ -88,26 +63,29 @@ $ou $domain $issuer + - mkcerthash "$filebase" - chown root:root "$SSLCERTDIR/$filename" - chmod 0640 "$SSLCERTDIR/$filename" + chown root:root "$keypath" "$certpath" + chmod 0600 "$keypath" + chmod 0644 "$certpath" + mkcerthash "$base" } mkkey() { filebase="$1" + keypath="$SSLKEYDIR/$filebase.pem" openssl genrsa \ - -out "$SSLKEYDIR/$filename" - chown root:root "$SSLKEYDIR/$filename" - chmod 0600 "$SSLKEYDIR/$filename" + -out "$keypath" > /dev/null 2>&1 + chown root:root "$keypath" + chmod 0600 "$keypath" } mkcertreq() { - filebase="$1" + base="$1" domain="$2" - filename="$filebase.pem" + keypath="$SSLKEYDIR/$base.pem" + reqpath="$SSLCERTDIR/$base.csr" openssl req -new \ - -key "$SSLKEYDIR/$filename" \ - -out "$SSLCERTDIR/$filename" > /dev/null 2>&1 <<+ + -key "$keypath" \ + -out "$reqpath" > /dev/null 2>&1 <<+ $cn $state $loc @@ -115,47 +93,116 @@ $org $ou $domain $issuer +. +. + - chown root:root "$SSLCERTDIR/$filename" - chmod 0640 "$SSLCERTDIR/$filename" + chown root:root "$reqpath" + chmod 0640 "$reqpath" } mkselfcacert() { - filebase="$1" + base="$1" domain="$2" cacert="$3" - filename="$filebase.pem" - reqfilename="$filebase.csr" - cafilename="$cacert.pem" + certpath="$SSLCERTDIR/$base.pem" + reqpath="$SSLCERTDIR/$base.csr" + cakeypath="$SSLKEYDIR/$cacert.pem" + cacertpath="$SSLCERTDIR/$cacert.pem" openssl x509 -req \ -days $DAYS2EXPIRE \ - -CA "$SSLCERTDIR/$cafilename" \ - -CAkey "$SSLKEYDIR/$cafilename" \ + -CA "$cacertpath" \ + -CAkey "$cakeyfile" \ -CAcreateserial \ - -in "$SSLCERTDIR/$reqfilename" \ - -out "$SSLCERTDIR/$filename" + -in "$reqpath" \ + -out "$certpath" + chown root:root "$certpath" + chmod 0644 "$certpath" + mkcerthash "$base" } mkcacert() { - filebase="$1" - filename="$filebase.pem" + base="$1" + cakeypath="$SSLKEYDIR/$base.pem" + cacertpath="$SSLCERTDIR/$base.pem" #FIXME: Make strength configurable openssl genrsa -des3 \ - -out "$SSLKEYDIR/$filename" 1024 - chown root:root "$SSLKEYDIR/$filename" - chmod 0400 "$SSLKEYDIR/$filename" + -out "$cakeypath" 1024 + chown root:root "$cakeypath" + chmod 0400 "$cakeypath" # Generate and pre-fill certification request #FIXME: Make validity configurable openssl req -new \ - -key "$SSLKEYDIR/$filename" \ + -key "$cakeypath" \ -x509 -days 1095 \ - -out "$SSLCERTDIR/$filename" + -out "$cacertpath" # Add hash to certified public certificate and cleanup - mkcerthash "$cacert" - chown root:root "$SSLCERTDIR/$filename" - chmod 0640 "$SSLCERTDIR/$filename" + chown root:root "$cacertpath" + chmod 0640 "$cacertpath" + mkcerthash "$base" +} + +usage() { +cat < [...] [--force] + or: $prg [...] [-f] + +General options: + --type Type of certificate + selfcert Self-certified certificate + ca Use Certificate Authority + selfca Homemade Certificate Authority + Default: try guess based on other options + --daemon Daemon(s) in need for a certificate + Default: none (or create only CA cert) + (selfcert certs are unique per daemon) + (ca and selfca are shared per hostname) + -f, --force Force overwriting existing certificate(s) + -h, --help This help text + +General certificate options: + --fqdn Fully Qualified Domain Name + Default: $FQDN + --cn Country Name (2 letter code) + Default: $CN + --state State or Province Name (full name) + Default: $STATE + --loc Locality Name (eg, city) + Default: $LOC + --org Organisation/company + Default: $ORG + --ou Organisational unit/department + Default: $OU + --issuer Email address of entity issuing certificate + Default: $ISSUER + +Selfcert options: + --cacert Certificate Authority to use/create + Default: \"cacert\" (possibly symlinked) + --makeca Create CA certificate if missing + +Examples: + + Create certs for UW imapd/popd, guessing hostname and certificate type: + $prg imapd pop3d + + Create host cert for non-default hostname and bind to UW daemons: + $prg --type selfca --fqdn mail.$DOMAINNAME imapd pop3d + + Create local CA, signed host cert for $HOSTNAME and links to UW daemons: + $prg --type selfca --cacert My-own_CA --makeca imapd pop3d + + Create certificate request to pass to Certificate Authority + $prg --type ca --fqdn mail.$DOMAINNAME + + Finish install of CA-certified host certificate + $prg --type ca --fqdn mail.$DOMAINNAME imapd pop3d +EOF +exit 1 } +certtype='' fqdn='' cn='' state='' @@ -165,13 +212,11 @@ ou='' daemon='' daemons='' issuer='' -cert='' cacert='' makeca='' force='' -args='' -TEMP=`getopt -o f --long help,fqdn:,cn:,state:,loc:,org:,ou:,daemon:,issuer:,cert,cacert:,makeca,force -n "$prg" -- "$@"` +TEMP=`getopt -o fh --long type:,daemon:,force,help,fqdn:,cn:,state:,loc:,org:,ou:,issuer:,cacert:,makeca -n "$prg" -- "$@"` # Check for non-GNU getopt if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi @@ -182,120 +227,147 @@ eval set -- "$TEMP" while true ; do case "$1" in - --help) usage;; + --type) case "$2" in + selfcert|ca|selfca) certtype="$2"; shift 2;; + *) echo "ERROR: Wrong certificate type: \"$2\"!" >&2; usage;; + esac;; + --daemon) daemons="$daemons$2 "; shift 2;; + --force|-f) force=1; shift;; + --help|-h) usage;; --fqdn) fqdn="$2"; shift 2;; --cn) cn="$2"; shift 2;; --state) state="$2"; shift 2;; --loc) loc="$2"; shift 2;; --org) org="$2"; shift 2;; --ou) ou="$2"; shift 2;; - --daemon) daemons="$daemons$2 "; shift 2;; --issuer) issuer="$2"; shift 2;; - --cert) cert=1; shift;; --cacert) cacert="$2"; shift 2;; --makeca) makeca=1; shift;; - --force|-f) force=1; shift;; --) shift; break;; - *) echo "Internal error!" ; exit 1 ;; + *) echo "Internal error!" >&2 ; exit 1 ;; esac done -if [ -z "$issuer" ]; then - DOMAINNAME="`hostname -d`" - ISSUER="postmaster@$DOMAINNAME" -fi +# Non-opt arguments are daemons +daemons="$daemons $@" -if [ -z "$fqdn" ]; then - if [ $# -gt 0 ]; then - fqdn="`hostname -f`" - else - echo "Too few parameters!" - usage - fi +# Try to pick a sane certificate type if not explicitly set +if [ -z "$certtype" ] && [ -n "$makeca" ]; then + certtype="selfca" +fi +if [ -z "$certtype" ] && [ -n "$cacert" ] && [ -r "$SSLKEYDIR/$cacert.pem" ]; then + certtype="selfca" +fi +if [ -z "$certtype" ]; then + echo "ERROR: Cannot guess certificate type!" >&2 + echo " Please explicitly set --type (or more other options)." >&2 + exit 1 fi -for val in org ou; do - if eval [ -z "\$$val" ]; then - eval "$val=\"$fqdn\"" - fi -done - -for val in cn state loc; do - if eval [ -z "\$$val" ]; then - eval "$val=\".\"" - fi -done +# Use defaults for empty vars, and export +cn="${cn:-$CN}" +state="${state:-$STATE}" +loc="${loc:-$LOC}" +org="${org:-$ORG}" +ou="${ou:-$OU}" +fqdn="${fqdn:-$FQDN}" +issuer="${issuer:-$ISSUER}" -if [ -n "$cert" ]; then - if [ ! -s "$SSLCERTDIR/$fqdn.pem" ] || [ ! -s "$SSLKEYDIR/$fqdn.pem" ]; then - echo "WARNING: Host certificate for \"$fqdn\" missing..." - if [ -z "$cacert" ]; then - echo "ERROR: The \"--cacert\" option is required when making a host certificate!" - exit 1 +# +case "$certtype" in + selfcert) + ;; + ca) + if [ ! -f "$SSLCERTDIR/$fqdn.pem" ] || [ -n "$force" ]; then + echo "Generating host key for \"$fqdn\"..." + mkkey "$fqdn" + echo "Generating certificate request for \"$fqdn\"..." + mkcertreq "$fqdn" "$fqdn" + echo "Certificate request generated: $SSLCERTDIR/$fqdn.csr!" + echo + echo "Now pass this request to you certificate authority, save their" + echo "provided certificate as \"$SSLCERTDIR/$fqdn.pem\"," + echo "and run this script again with same options (except --force)." + exit 0 + else + echo "Hashing new certificate and cleanup..." + mkcerthash "$fqdn" + rm -f "$SSLCERTDIR/$fqdn.csr" + fi + ;; + selfca) + if [ -z "$cacert" ] || [ "$cacert" = "cacert" ]; then + if ! cacert="$(basename "$(readlink "$SSLCERTDIR/cacert.pem")")"; then #" mc syntax hilite bug workaround + echo "ERROR: Cannot resolve default CA cert \"$SSLCERTDIR/cacert.pem\"" >&2 + exit 1 + fi + fi + if [ ! -s "$SSLCERTDIR/$fqdn.pem" ] || [ ! -s "$SSLKEYDIR/$fqdn.pem" ]; then + if [ -n "$force" ]; then + echo "WARNING: Host certificate missing - will be (re)created!" >&2 + else + echo "Error: Host certificate is missing!" >&2 + echo " Use \"--cacert\" to (re)create." >&2 + exit 1 + fi fi # Cleaning up - if allowed for file in "$SSLKEYDIR/$fqdn.pem" "$SSLCERTDIR/$fqdn.csr" "$SSLCERTDIR/$fqdn.pem"; do - if [ -e "$file" ]; then - if [ -n "$force" ]; then - rm -f "$file" - else - echo "ERROR: File \"$file\" already exists!" - exit 1 - fi + if [ -n "$force" ] && [ -e "$file" ]; then + rm -f "$file" + else + echo "ERROR: File \"$file\" already exists!" >&2 + echo " Use \"--force\" to override." >&2 + exit 1 fi done if [ ! -s "$SSLCERTDIR/$cacert.pem" ] || [ ! -s "$SSLKEYDIR/$cacert.pem" ]; then - echo "WARNING: CAcert (certifying authority certificate) missing..." - if [ -z "$makeca" ]; then - echo "ERROR: The \"--makeca\" option is required when making a CAcert!" + if [ -z "$makeca"]; then + echo "Error: CA certificate is missing!" >&2 + echo " Use another \"--cacert\" or create with \"--makeca\"." >&2 exit 1 fi - # Generate private key for CA certificate echo "Generating CAcert \"$cacert\"..." mkcacert "$cacert" fi - echo "Generating host certificate for \"$fqdn\"..." - # Generate private key for host certificate + echo "Generating host key for \"$fqdn\"..." mkkey "$fqdn" - # Generate and pre-fill certification request + echo "Generating host certificate for \"$fqdn\"..." mkcertreq "$fqdn" "$fqdn" - if [ -n "$cacert" ]; then - # Generate public certificate from certification request - mkselfcacert "$fqdn" "$fqdn" "$cacert" - elif [ ! -f "$SSLCERTDIR/$fqdn.pem" ]; then - echo "Certificate request generated: $SSLCERTDIR/$fqdn.csr" - echo "Now pass the request to you certificate authority, save their" - echo "provided certificate as \"$SSLCERTDIR/$fqdn.pem\"," - echo "and run this script with same options again." - exit 0 - fi - if [ ! -f "$SSLCERTDIR/$fqdn.pem" ]; then - echo "ERROR: certificate "$SSLCERTDIR/$fqdn.pem" not found!" - exit 1 - fi - # Add hash to certified public certificate and cleanup - mkcerthash "$fqdn" + mkselfcacert "$fqdn" "$fqdn" "$cacert" rm "$SSLCERTDIR/$fqdn.csr" - fi -fi + ;; + *) + echo "Internal error!" >&2 + exit 1 + ;; +esac -for daemon in $daemons $@; do +for daemon in $daemons; do if [ -f "$SSLCERTDIR/$daemon.pem" ]; then if [ -n "$force" ]; then + echo "WARNING: Removing exisitng certificate \"/etc/ssl/certs/$daemon.pem\"." >&2 rm -f "$SSLCERTDIR/$(openssl x509 -noout -hash < "$SSLCERTDIR/$daemon.pem").0" rm -f "$SSLCERTDIR/$daemon.pem" else - echo "Ignoring certificate (/etc/ssl/certs/$daemon.pem already exists...)" + echo "WARNING: Ignoring already existing certificate \"/etc/ssl/certs/$daemon.pem\"." >&2 continue fi fi - if [ -n "$cert" ]; then - echo "Attaching $daemon to certified certificate for $fqdn." - ln -sf "$fqdn.pem" "$SSLCERTDIR/$daemon.pem" - ln -sf "$fqdn.pem" "$SSLKEYDIR/$daemon.pem" - else - echo -n "Generating self-certifying $daemon certificate..." - mkselfcert "$daemon" "$fqdn" - echo "Done!" - fi + case "$certtype" in + selfcert) + echo -n "Generating self-certifying $daemon certificate..." + mkselfcert "$daemon" "$fqdn" + echo "Done!" + ;; + ca|selfca) + echo "Attaching $daemon to certified certificate for $fqdn." + ln -sf "$fqdn.pem" "$SSLCERTDIR/$daemon.pem" + ln -sf "$fqdn.pem" "$SSLKEYDIR/$daemon.pem" + ;; + *) + echo "Internal error!" >&2 + exit 1 + ;; + esac done -- cgit v1.2.3