#!/bin/bash # OpenSLX SSL Certificate management if ! mkdir "/run/openslx-cert-manager"; then echo "Already in progress." exit 1 fi trap 'rm -rf -- /run/openslx-cert-manager' EXIT declare -rg BASE="/etc/ssl/openslx" declare -rg PRIV="$BASE/private" declare -rg CERT="$BASE/cert" declare -rg LIGHT="$BASE/lighttpd" mkdir -p "$BASE" "$PRIV" "$CERT" chown -R root:root "$BASE" || exit 1 chmod u+rwx,go+rx-w "$BASE" "$CERT" || exit 1 chmod u+rwx,go-rwx "$PRIV" || exit 1 # Before doing anything, make sure we have a CA with enough validity left # File name format for ca is: # ${PRIV}/ca-FFFFFFFFFF-TTTTTTTTTT.key # ${CERT}/ca-TTTTTTTTTT.crt # Where TT is the unix timestamp of "validTo" of that cert # And FF is the unix timestamp of when we should starting using a CA to # sign our certificates. This is for a grace period between CA certs. # We deliver a new CA certificate immediately when it was generated, but # only start signing server certificates with it after a grace period of # 180 days. Any client that rebooted within those 180 days will not run # into any certificate issues, but if you wanted to cover that case too # you could make it so the client re-downloads trusted CA-certs every # couple days. declare -rg NOW="$( date +%s )" # PROD declare -rg ca_days="$(( 10 * 365 ))" # 10y declare -rg ca_min_remain_s="$(( 400 * 86400 ))" # bit more than 1y declare -rg ca_new_expire_ts="$(( ca_days * 86400 + NOW ))" declare -rg srv_days=365 # 1y declare -rg srv_min_remain_s="$(( 180 * 86400 ))" # half a year declare -rg srv_new_ts="$(( srv_days * 86400 + NOW ))" # TEST #declare -rg ca_days=1825 # 5y #declare -rg ca_min_remain_s="$(( 1260 ))" # bit more than 1y #declare -rg ca_new_expire_ts="$(( 1320 + NOW ))" #declare -rg srv_days=365 # 1y #declare -rg srv_min_remain_s="$(( 1200 ))" # half a year #declare -rg srv_new_ts="$(( 1230 + NOW ))" get_ts () { ts="${1%.*}" ts="${ts##*/ca-}" ts="${ts##*/srv-}" from="${ts%-*}" if [ "$from" = "$ts" ]; then from= else ts="${ts#*-}" fi } create_conf () { ca_dir="$( mktemp -d /tmp/bwlp-XXXXXXXX )" [ -z "$ca_dir" ] && exit 1 mkdir "$ca_dir"/{certs,crl,newcerts,private} touch "$ca_dir"/index.txt ca_config="$ca_dir/openssl.cnf" cp -f "/etc/ssl/openssl.cnf" "$ca_config" cat >> "$ca_config" <<-MYCA [ CA_openslx ] dir = $ca_dir certs = \$dir/certs crl_dir = \$dir/crl database = \$dir/index.txt new_certs_dir = \$dir/newcerts serial = \$dir/serial crl = \$dir/crl.pem x509_extensions = usr_cert name_opt = ca_default cert_opt = ca_default default_md = default preserve = no policy = policy_match MYCA } latest_ca_file= ca_last= for i in "${PRIV}"/ca-??????????.key; do [ -s "$i" ] || continue get_ts "$i" if ! [ -s "${CERT}/ca-${ts}.crt" ] \ || ! [ -s "${CERT}/intermediate-${ts}.crt" ] \ || ! [ -s "${PRIV}/intermediate.key" ] \ || (( ts < NOW )); then # Missing cert, or expired -> delete rm -f -- "${CERT}/ca-${ts}.crt" "${PRIV}/ca-${ts}.key" "${CERT}/intermediate-${ts}.crt" continue fi ca_last="$ts" latest_ca_file="${CERT}/ca-${ts}.crt" done mknew= if [ -z "$ca_last" ] || (( NOW + ca_min_remain_s > ca_last )); then # Make new CA echo "Creating new CA..." openssl req -new -newkey rsa:4096 -x509 -days "$ca_days" -extensions v3_ca \ -nodes -subj "/C=DE/ST=PewPew/L=HeyHey/O=bwLehrpool/CN=ca-${NOW}.bwlehrpool" \ -keyout "${PRIV}/ca-${ca_new_expire_ts}.key" -out "${CERT}/ca-${ca_new_expire_ts}.crt" || exit 2 mknew=1 # # Create new intermediate, sign with all CAs csr="$( mktemp /tmp/bwlp-XXXXXXX.csr )" # Create request, CA:TRUE echo "Generate intermediate key+CSR..." [ -s "${PRIV}/intermediate.key" ] || openssl genrsa -out "${PRIV}/intermediate.key" 4096 openssl req -new -key "${PRIV}/intermediate.key" \ -nodes -subj "/C=DE/ST=PewPew/L=HeyHey/O=bwLehrpool/CN=intermediate.bwlehrpool" \ -out "$csr" || exit 2 create_conf # Sign request, CA:TRUE echo "Sign new intermediate key with CA..." openssl ca -config "$ca_config" -extensions v3_ca -create_serial \ -policy policy_anything -days "$ca_days" \ -cert "${CERT}/ca-${ca_new_expire_ts}.crt" -keyfile "${PRIV}/ca-${ca_new_expire_ts}.key" \ -notext -name CA_openslx -batch -out "${CERT}/intermediate-${ca_new_expire_ts}.crt" -in "$csr" || exit 2 rm -rf -- "$ca_dir" "$csr" fi if [ -n "$mknew" ] || ! [ -s "/opt/openslx/configs/modules/self-signed-ca.tar" ] \ || [ "/opt/openslx/configs/modules/self-signed-ca.tar" -ot "$latest_ca_file" ]; then # Rebuild config module for clients echo "Updating client config module..." ( tmpdir="$( mktemp -d '/tmp/bwlp-XXXXXXX' )" cp -a "${CERT}/"ca-*.crt "$tmpdir/" cd "$tmpdir/" || exit 6 openssl rehash . tar -c -k -f "/opt/openslx/configs/modules/self-signed-ca.tar" \ --transform 's#^[./][./]*#/opt/openslx/ssl/#' . cd /tmp || exit 7 rm -rf -- "$tmpdir" sudo -u www-data -n php /srv/openslx/www/slx-admin/api.php sysconfig --action rebuild echo "." ) fi # Now check the server certificate declare -a srv_list srv_list=() for i in "${PRIV}"/srv-??????????.key; do [ -s "$i" ] || continue get_ts "$i" if (( ts < NOW )) || ! [ -s "${CERT}/srv-${ts}.crt" ]; then rm -f -- "$i" "${CERT}/srv-${ts}.crt" continue fi srv_list+=( "$ts" ) done if [ -n "$mknew" ] || [ "${#srv_list[@]}" = 0 ] \ || [ "$(( NOW + srv_min_remain_s ))" -gt "${srv_list[-1]}" ]; then # Request ServerCert csr="$( mktemp /tmp/bwlp-XXXXXXX.csr )" echo "Generating new Server Certificate. Key+CSR..." rm -f -- "${CERT}"/srv-*.crt "${PRIV}/srv.key.tmp" "${PRIV}"/srv-*.key openssl req -new -nodes -keyout "${PRIV}/srv.key.tmp" -out "$csr" \ -subj "/C=DE/ST=PewPew/L=HeyHey/O=bwLehrpool/CN=satellite.bwlehrpool" || exit 4 echo "Signing Server Certificate with intermediate..." declare -a in_list in_list=() for i in "${CERT}"/intermediate-??????????.crt; do [ -s "$i" ] || continue get_ts "$i" if (( ts < NOW )); then echo "Expired intermediate $i" rm -f -- "$i" continue fi echo "Have intermediate $i" in_list+=( "$i" ) done if [ "${#in_list[@]}" = 0 ]; then echo "ERROR: Have no intermediate certificate" exit 11 fi for in_cert in "${in_list[@]}"; do get_ts "$in_cert" (( ts < 30 * 86400 + NOW )) && continue # Expiring in a month, ignore break # Need only one really done echo "Signing with $in_cert" create_conf # Need extfile for SAN, chromium doesn't honor CN anymore cat > "${csr}.cnf" <<-END basicConstraints = CA:FALSE nsCertType = server nsComment = "OpenSSL Generated Server Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = satellite.bwlehrpool END openssl ca -config "$ca_config" -create_serial -policy policy_anything -days "$srv_days" \ -cert "$in_cert" -keyfile "${PRIV}/intermediate.key" -extfile "${csr}.cnf" \ -notext -name CA_openslx -batch -out "${CERT}/srv-${srv_new_ts}.crt" -in "$csr" || exit 4 rm -rf -- "$ca_dir" rm -f -- "$csr" "${csr}.cnf" mv "${PRIV}/srv.key.tmp" "${PRIV}/srv-${srv_new_ts}.key" || exit 5 srv_list+=( "$srv_new_ts" ) # Combine and prepare for lighttpd mkdir -p "$LIGHT" || exit 10 # Combine cert and key, as required by lighttpd echo "Writing out lighttpd PEMs..." cat "${CERT}/srv-${srv_new_ts}.crt" "${PRIV}/srv-${srv_new_ts}.key" > "${LIGHT}/server.pem" || exit 10 chmod 0600 "${LIGHT}/server.pem" # Create ca-chain cat "${in_list[@]}" > "${LIGHT}/ca-chain.pem" if [ "$1" = "--restart" ] || [ -t 0 ]; then echo "Restarting lighttpd..." systemctl restart lighttpd.service fi fi echo "Done." exit 0