diff --git a/ssl_mgmt/README b/ssl_mgmt/README new file mode 100644 index 0000000..72a79c4 --- /dev/null +++ b/ssl_mgmt/README @@ -0,0 +1,70 @@ +ssl_mgmt is a helper to manage ssl certificate: creation, renewal and removal. +So far, only renewal is supported. + +ssl_mgmt refers to certificates through the service they are associated with. + +*** EXAMPLES *** + +To renew the certificate associated to https, the command line is: +ssl_mgmt renew https + +If you want to renew certificates of all services, you should do: +ssl_mgmt renew all + +Note: This suppose that + * all services are listed in /root/homemade_certs; + * directories have special rights so that newly created certificates + automatically get proper rights; + * Root CA is already created + * openssl.cnf.in is copied at the root of the CA hierarchy + +The file system hierarchy assumed is: +lrwxrwxrwx 1 root root 14 6 janv. 2010 certs -> /etc/ssl/certs +drws--S---+ 2 root ssl-cert 4096 23 janv. 2011 csr +-rw------- 1 root ssl-cert 1937 20 févr. 16:38 index.txt +-rw------- 1 root ssl-cert 20 20 févr. 16:38 index.txt.attr +drwSr-Sr--+ 2 root ssl-cert 4096 20 févr. 16:38 newcerts +drwSr-S---+ 2 root ssl-cert 4096 20 févr. 16:38 newkeys +-rw-r--r-- 1 root ssl-cert 1546 20 févr. 14:24 openssl.cnf.in +lrwxrwxrwx 1 root root 16 6 janv. 2010 private -> /etc/ssl/private +-rw------- 1 root ssl-cert 3 20 févr. 16:38 serial + +About csr, newcerts and newkeys: + +# file: usr/lib/ssl/CA/csr +# owner: root +# group: ssl-cert +# flags: ss- +user::rwx +group::--- +other::--- +default:user::rw- +default:group::r-- +default:other::--- + +# file: usr/lib/ssl/CA/newcerts +# owner: root +# group: ssl-cert +# flags: ss- +user::rw- +group::r-- +other::r-- +default:user::rw- +default:group::r-- +default:other::r-- + +# file: usr/lib/ssl/CA/newkeys +# owner: root +# group: ssl-cert +# flags: ss- +user::rw- +group::r-- +other::--- +default:user::rw- +default:group::r-- +default:other::--- + +To use this script, you have to be root, or to be able to execute +commands with root privileges through sudo. +You should install it in a directory within the PATH of the root user, +such as /usr/local/sbin. diff --git a/ssl_mgmt/openssl.cnf.in b/ssl_mgmt/openssl.cnf.in new file mode 100644 index 0000000..3ce6cb8 --- /dev/null +++ b/ssl_mgmt/openssl.cnf.in @@ -0,0 +1,62 @@ +# +# OpenSSL configuration file. +# + +# Establish working directory +dir = . + +[ ca ] +default_ca = CA_Default + +[ CA_Default ] +serial = $dir/serial +database = $dir/index.txt +new_certs_dir = $dir/newcerts +certificate = $dir/certs/ca-cert.pem +private_key = $dir/private/ca-key.pem +default_days = #LENGTH# # Certificates are signed for default_days days +default_md = md5 +preserve = no +email_in_dn = no +nameopt = default_ca +certopt = default_ca +policy = policy_match +copy_extensions = copy + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +default_bits = 2048 # Taille des clés +default_keyfile = newkeys/key.pem # Nom de la clé généré (à spécifier en ligne de commande si différent) +default_md = md5 # Algorithme de résumé (hash) +string_mask = nombstr # Caractères authorisés +prompt = no +distinguished_name = req_distinguished_name +req_extensions = v3_req +# On ne veut ces extensions que pour générer les certificats racines +# donc on le spécifie sur la ligne de commande +x509_extensions = v3_ca + +[ req_distinguished_name ] +organizationName = #ORG# +organizationalUnitName = #ORGUNIT# +localityName = #LOCALITY# +stateOrProvinceName = #STATE# +countryName = #COUNTRY# +commonName = #COMMONNAME# + +[ v3_ca ] +basicConstraints = CA:TRUE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always + +[ v3_req ] +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash +subjectAltName = #ALTNAME# diff --git a/ssl_mgmt/ssl_mgmt b/ssl_mgmt/ssl_mgmt new file mode 100755 index 0000000..51456f7 --- /dev/null +++ b/ssl_mgmt/ssl_mgmt @@ -0,0 +1,253 @@ +#!/bin/sh +# +# ssl_mgmt, Copyright © 2012 Thomas Preud'homme +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# email_account is a helper to create e-mail accounts in a configuration +# using Postfix mail transport agent with vhosts and Cyrus IMAP server, +# in a Debian environment. + +set -u + +confFile=openssl.cnf + +# Display usage. +usage () +{ + local - progname + progname=$1 + echo "Usage :" + echo + echo "$progname renew " + echo "$progname -h" + echo + echo "First form renew the certificate of the given service" + echo + echo "Second form prints this help." +} + +# @param question Question to ask +# @return 0 if answer is positive, 1 else +# +# Ask question to the user with a default choice set to no. +ask_user_default_no () +{ + local - answer + answer="unset" + echo -n "$1 [y/N] " + while [ -n "$answer" -a "$answer" != "y" -a "$answer" != "Y" -a "$answer" != "n" -a "$answer" != "N" ] + do + read answer + if [ -z "$answer" -o "$answer" = "n" -o "$answer" = "N" ] + then + return 1 + fi + if [ "$answer" = "y" -o "$answer" = "Y" ] + then + break + fi + echo -n "$2 [y/N] " + done + return 0 +} + +# Test number of argument is correct and their value are correct also. +test_args () +{ + local - user domain + while getopts "h" opt + do + case $opt in + "h") + if [ $# -gt 1 ] + then + echo "Error! Too many arguments." >&2 + exit 1 + fi + usage $(basename "$0") + exit 0 ;; + esac + done + if [ $# -ne 2 -o "$1" != "renew" ] + then + usage $(basename "$0") + exit 0 + fi + eval service="\$$((OPTIND+1))" +} + +# Are we root? +must_sudo () +{ + uid="$(id -u)" + [ ! $uid -eq 0 ] + return $? +} + +# This function tries to become root with sudo and execute this script. +# NB: This function doesn't return. +try_sudo () +{ + local - ret + echo "You aren't root. Trying to use sudo to become root…" + sudo $0 "$@" # Try to execute the script with sudo + ret=$? + if [ ! $ret -eq 0 ] + then + echo "Error! You must be root or being able to become root by sudo without password to create an email account or add an email alias." >&2 + fi + exit $ret +} + +get_field_from_line () +{ + local - line field result + + line="$1" + field="$2" + result="${line#*$field=}" + echo "${result%%/*}" +} + +get_cert_params () +{ + local - subject issuer dates ext fromDate toDate + + certFile="/etc/ssl/certs/${service}-cert.pem" + subject="$(openssl x509 -in "$certFile" -noout -subject)" + dates="$(openssl x509 -in "$certFile" -noout -dates)" + exclNoExt="-certopt no_header,no_version,no_serial,no_signame" + exclNoExt="$exclNoExt,no_validity,no_subject,no_issuer,no_pubkey" + exclNoExt="$exclNoExt,no_sigdump,no_aux" + altName="$(openssl x509 -in "$certFile" -text $exclNoExt | while read ext + do + if [ "$ext" = "X509v3 Subject Alternative Name:" ] + then + read altName + echo $altName + break + fi + done)" + country=$(get_field_from_line "$subject" "C") + state=$(get_field_from_line "$subject" "ST") + city=$(get_field_from_line "$subject" "L") + organization=$(get_field_from_line "$subject" "O") + unit=$(get_field_from_line "$subject" "OU") + commonName=$(get_field_from_line "$subject" "CN") + fromDate=${dates#*notBefore=} + fromDate=${fromDate%notAfter*} + fromDate=$(date -d "$fromDate" "+%s") + toDate=${dates#*notAfter=} + toDate=$(date -d "$toDate" "+%s") + days=$(($toDate-$fromDate)) + days=$(($days/86400)) +} + +add_to_replace_cmd () +{ + local - replaceCmd key value + + replaceCmd="$1" + key="$2" + value="$3" + if [ -n "$value" ] + then + echo "$replaceCmd${replaceCmd:+;}s/$key/$value/" + fi +} + +generate_config () +{ + local - replaceCmd cnfTmpFile + + replaceCmd="$(add_to_replace_cmd "${replaceCmd:-}" "#LENGTH#" "${days:-}")" + replaceCmd="$(add_to_replace_cmd "$replaceCmd" "#ORG#" "${organization:-}")" + replaceCmd="$(add_to_replace_cmd "$replaceCmd" "#ORGUNIT#" "${unit:-}")" + replaceCmd="$(add_to_replace_cmd "$replaceCmd" "#LOCALITY#" "${city:-}")" + replaceCmd="$(add_to_replace_cmd "$replaceCmd" "#STATE#" "${state:-}")" + replaceCmd="$(add_to_replace_cmd "$replaceCmd" "#COUNTRY#" "${country:-}")" + replaceCmd="$(add_to_replace_cmd "$replaceCmd" "#COMMONNAME#" "${commonName:-}")" + replaceCmd="$(add_to_replace_cmd "$replaceCmd" "#ALTNAME#" "${altName:-}")" + sed "$replaceCmd" $confFile.in > $confFile + if ask_user_default_no "Do you want to edit the openssl configuration file?" + then + if [ -z "${EDITOR:-}" ] + then + $EDITOR $confFile + else + editor $confFile + fi + fi + cnfTmpFile="$(mktemp --tmpdir=. openssl.cnf.XXXXXXXXXX)" + mv $confFile $cnfTmpFile + grep -v "#[A-Z]\+#" $cnfTmpFile > $confFile + rm $cnfTmpFile +} + +generate_cert () +{ + openssl req -new -nodes -out csr/${service}-req.pem -keyout newkeys/${service}-key.pem -config $confFile + openssl req -in csr/${service}-req.pem -text -verify -noout + if ! ask_user_default_no "Is the Certificate Signing Request correct?" + then + return 1 + fi + mv newkeys/${service}-key.pem private + openssl ca -batch -out newcerts/${service}-cert.pem -config $confFile -passin file:/root/passwords/root_ca -infiles csr/${service}-req.pem + mv newcerts/${service}-cert.pem certs + cat private/${service}-key.pem certs/${service}-cert.pem > private/${service}-keycert.pem + return 0 +} + +main () +{ + local - ret servicesok + ret=0 + test_args "$@" + # This test should be useless if rights on this file are corrects + # (that is 770 for root:gt owner) + if must_sudo + then + try_sudo "$@" + fi + cd /usr/lib/ssl/CA/ + if [ "${service}" = "all" ] + then + services="" + while read service + do + services="$services $service" + done